This commit is contained in:
despiegk 2025-05-23 15:28:30 +04:00
parent 92b9c356b8
commit 0e545e56de
144 changed files with 294 additions and 1907 deletions

View File

@ -1,23 +1,3 @@
// @title HeroLauncher API
// @version 1.0
// @description HeroLauncher API provides endpoints for managing services, processes, and system resources
// @termsOfService http://swagger.io/terms/
// @contact.name HeroLauncher Support
// @contact.url https://github.com/freeflowuniverse/heroagent
// @contact.email support@heroagent.io
// @license.name MIT
// @license.url https://opensource.org/licenses/MIT
// @host localhost:9021
// @BasePath /
// @schemes http https
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization
package main
import (
@ -26,8 +6,7 @@ import (
"log"
"os"
"github.com/freeflowuniverse/heroagent/pkg/heroagent"
_ "github.com/freeflowuniverse/heroagent/pkg/heroagent/docs" // Import generated swagger docs
"git.ourworld.tf/herocode/heroagent/pkg/heroagent"
)
func main() {

4
go.mod
View File

@ -12,7 +12,7 @@ require (
github.com/gofiber/fiber/v2 v2.52.6
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/redis/go-redis/v9 v9.8.0
github.com/shirou/gopsutil/v3 v3.24.5
github.com/tidwall/redcon v1.6.2
github.com/yuin/goldmark v1.7.8
@ -80,8 +80,6 @@ require (
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/speakeasy-api/jsonpath v0.6.1 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/swaggo/files/v2 v2.0.2 // indirect
github.com/swaggo/swag v1.16.4 // indirect
github.com/tidwall/btree v1.1.0 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.1.1 // indirect

202
go.sum Normal file
View File

@ -0,0 +1,202 @@
9fans.net/go v0.0.2/go.mod h1:lfPdxjq9v8pVQXUMBCx5EO5oLXWQFlKRQgs1kEkjoIM=
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
github.com/CloudyKit/jet/v6 v6.3.1/go.mod h1:lf8ksdNsxZt7/yH/3n4vJQWA9RUq4wpaHtArHhGVMOw=
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM=
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.2.1/go.mod h1:ZwHcC/82TOaovDi//J/804umJFFmbOHPngi8iYYv/Eo=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
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/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/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/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/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/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/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/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
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/go.mod h1:mI8iBx3RAODwX7PJJ7qzsKAKs/vY429YfS2/9wKnDbQ=
github.com/fhs/mux9p v0.3.1/go.mod h1:F4hwdenmit0WDoNVT2VMWlLJrBVCp/8UhzJa7scfjEQ=
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
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.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
github.com/gofiber/template v1.8.3/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8=
github.com/gofiber/template/jet/v2 v2.1.11/go.mod h1:Kb1oBdrx90oEvP71MDTUB9k+IWRF082Td5OPW7SoUMQ=
github.com/gofiber/template/pug/v2 v2.1.8/go.mod h1:e0Sg0YBMtC+RQMRm0swaAvqIBDJmhhDIKfFFtQRjvlQ=
github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0=
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/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
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.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/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/knusbaum/go9p v1.18.0/go.mod h1:HtMoJKqZUe1Oqag5uJqG5RKQ9gWPSP+wolsnLLv44r8=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
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/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.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
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/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/metoro-io/mcp-golang v0.8.0/go.mod h1:ifLP9ZzKpN1UqFWNTpAHOqSvNkMK6b7d1FSZ5Lu0lN0=
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
github.com/openai/openai-go v0.1.0-beta.9/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y=
github.com/pb33f/libopenapi v0.21.8/go.mod h1:Gc8oQkjr2InxwumK0zOBtKN9gIlv9L2VmSVIUk2YxcU=
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/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/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/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI=
github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
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/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/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
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/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
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/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/teambition/rrule-go v1.8.2/go.mod h1:Ieq5AbrKGciP1V//Wq8ktsTXwSwJHDD5mD/wLBGl3p4=
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/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
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/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/redcon v1.6.2/go.mod h1:p5Wbsgeyi2VSTBWOcA5vRXrOb9arFTcU2+ZzFjqV75Y=
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/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
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/go.mod h1:DbzwytT4g/odXquuOCqroKvtxxldI4nb3nuesHF/Exo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
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/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/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/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-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.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/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-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/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.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-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/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=

View File

@ -1,192 +0,0 @@
# Process Manager
The Process Manager is a component that manages and monitors external processes. It provides functionality to start, stop, restart, and monitor processes, as well as retrieve their status information such as CPU and memory usage.
## Features
- Start, stop, restart, and delete processes
- Monitor CPU and memory usage of managed processes
- Set deadlines for process execution
- Support for cron-like scheduling
- Telnet interface for remote management
- Authentication via secret key
## Components
The Process Manager consists of the following components:
1. **Process Manager Core**: Manages processes and their lifecycle
2. **Telnet Server**: Provides a telnet interface for remote management
3. **Client Library**: Provides a Go API for interacting with the Process Manager
4. **Command-line Client**: Provides a command-line interface for managing processes
5. **Interfaces**: Various interface implementations for interacting with the Process Manager
## Interfaces
The Process Manager provides several interfaces for integration and extension:
### ProcessManagerInterface
The core interface that defines all process management operations. This interface allows for dependency injection and easier testing.
```go
type ProcessManagerInterface interface {
// Authentication
GetSecret() string
// Process management
StartProcess(name, command string, logEnabled bool, deadline int, cron, jobID string) error
StopProcess(name string) error
RestartProcess(name string) error
DeleteProcess(name string) error
GetProcessStatus(name string) (*ProcessInfo, error)
ListProcesses() []*ProcessInfo
GetProcessLogs(name string, lines int) (string, error)
}
```
### Interface Implementations
The Process Manager includes the following interface implementations:
1. **Telnet Interface**: Provides a telnet-based interface for remote management
- Located in `pkg/processmanager/interfaces/telnet`
- Allows command execution via heroscript syntax
- Supports authentication and secure communication
2. **OpenRPC Interface**: Provides a JSON-RPC 2.0 interface following the OpenRPC specification
- Located in `pkg/processmanager/interfaces/openrpc`
- Enables programmatic access via HTTP or WebSockets
- Includes auto-generated client libraries
- Provides a standardized API schema
## Usage
### Starting the Process Manager
```bash
./processmanager -socket /tmp/processmanager.sock -secret mysecretkey
```
### Using the Command-line Client
```bash
# Start a process
./pmclient -socket /tmp/processmanager.sock -secret mysecretkey start -name myprocess -command "echo hello world" -log
# List all processes
./pmclient -socket /tmp/processmanager.sock -secret mysecretkey list -format json
# Get process status
./pmclient -socket /tmp/processmanager.sock -secret mysecretkey status -name myprocess -format json
# Stop a process
./pmclient -socket /tmp/processmanager.sock -secret mysecretkey stop -name myprocess
# Restart a process
./pmclient -socket /tmp/processmanager.sock -secret mysecretkey restart -name myprocess
# Delete a process
./pmclient -socket /tmp/processmanager.sock -secret mysecretkey delete -name myprocess
```
### Using the Telnet Interface
You can connect to the Process Manager using a telnet client:
```bash
telnet /tmp/processmanager.sock
```
After connecting, you need to authenticate with the secret key:
```
mysecretkey
```
Once authenticated, you can send heroscript commands:
```
!!process.start name:'myprocess' command:'echo hello world' log:true
!!process.list format:json
!!process.status name:'myprocess' format:json
!!process.stop name:'myprocess'
!!process.restart name:'myprocess'
!!process.delete name:'myprocess'
```
## Heroscript Commands
The Process Manager supports the following heroscript commands:
### process.start
Starts a new process.
```
!!process.start name:'processname' command:'command which can be multiline' log:true deadline:30 cron:'0 0 * * *' jobid:'e42'
```
Parameters:
- `name`: Name of the process (required)
- `command`: Command to run (required)
- `log`: Enable logging (optional, default: false)
- `deadline`: Deadline in seconds (optional)
- `cron`: Cron schedule (optional)
- `jobid`: Job ID (optional)
### process.list
Lists all processes.
```
!!process.list format:json
```
Parameters:
- `format`: Output format (optional, values: json or empty for text)
### process.delete
Deletes a process.
```
!!process.delete name:'processname'
```
Parameters:
- `name`: Name of the process (required)
### process.status
Gets the status of a process.
```
!!process.status name:'processname' format:json
```
Parameters:
- `name`: Name of the process (required)
- `format`: Output format (optional, values: json or empty for text)
### process.restart
Restarts a process.
```
!!process.restart name:'processname'
```
Parameters:
- `name`: Name of the process (required)
### process.stop
Stops a process.
```
!!process.stop name:'processname'
```
Parameters:
- `name`: Name of the process (required)

View File

@ -1,85 +0,0 @@
# Process Manager OpenRPC Example
This example demonstrates how to use the Process Manager OpenRPC interface to interact with the process manager. It provides a complete working example of both server and client implementations, with a mock process manager for testing purposes.
## Overview
The example includes:
1. A server that exposes the process manager functionality via OpenRPC over a Unix socket
2. A client that connects to the server and performs various operations
3. A mock process manager implementation for testing without requiring actual processes
## Structure
- `main.go`: The main entry point that runs both the server and client in the same process
- `mock_processmanager.go`: A mock implementation of the `ProcessManagerInterface` that simulates process operations
- `example_client.go`: Client implementation demonstrating how to use the OpenRPC client to interact with the process manager
## How to Run
```bash
# From the heroagent root directory
go run ./pkg/processmanager/examples/openrpc
```
The example will:
1. Start a server using a Unix socket at `/tmp/process-manager.sock`
2. Run a series of client operations against the server
3. Display the results of each operation
4. Clean up and shut down when complete
## What It Demonstrates
This example shows:
1. How to initialize and start an OpenRPC server for the process manager
2. How to create and use an OpenRPC client to interact with the process manager
3. How to perform common operations like:
- Starting a process (`StartProcess`)
- Getting process status (`GetProcessStatus`)
- Listing all processes (`ListProcesses`)
- Getting process logs (`GetProcessLogs`)
- Restarting a process (`RestartProcess`)
- Stopping a process (`StopProcess`)
- Deleting a process (`DeleteProcess`)
4. How to handle the response types returned by each operation
5. Proper error handling for RPC operations
## Notes
- This example uses a mock process manager implementation for demonstration purposes
- In a real application, you would use the actual process manager implementation
- The server and client run in the same process for simplicity, but they could be in separate processes
- The Unix socket communication can be replaced with other transport mechanisms if needed
## Implementation Details
### Server
The server is implemented using the `openrpc.Server` type, which wraps the OpenRPC manager and Unix socket server. It:
1. Creates a Unix socket at the specified path
2. Registers handlers for each RPC method
3. Authenticates requests using a secret
4. Marshals/unmarshals JSON-RPC requests and responses
### Client
The client is implemented using the `openrpc.Client` type, which provides methods for each operation. It:
1. Connects to the Unix socket
2. Sends JSON-RPC requests with the appropriate parameters
3. Handles authentication using the secret
4. Parses the responses into appropriate result types
### Mock Process Manager
The mock process manager implements the `interfaces.ProcessManagerInterface` and simulates:
1. Process creation and management
2. Status tracking
3. Log collection
4. Error handling
This allows testing the OpenRPC interface without requiring actual processes to be run.

View File

@ -1,149 +0,0 @@
package main
import (
"fmt"
"log"
"time"
"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
func RunClientExample(socketPath, secret string) error {
// Create a new client
client := openrpc.NewClient(socketPath, secret)
log.Println("🚀 Starting example process...")
// Start a process
result, err := client.StartProcess("example-process", "sleep 60", true, 0, "", "")
if err != nil {
return fmt.Errorf("failed to start process: %w", err)
}
log.Printf("Start result: success=%v, message=%s, PID=%d", result.Success, result.Message, result.PID)
// Wait a bit for the process to start
time.Sleep(500 * time.Millisecond)
log.Println("📊 Getting process status...")
// Get the process status
status, err := client.GetProcessStatus("example-process", "json")
if err != nil {
return fmt.Errorf("failed to get process status: %w", err)
}
printProcessStatus(status.(interfaces.ProcessStatus))
log.Println("📋 Listing all processes...")
// List all processes
processList, err := client.ListProcesses("json")
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{}:
log.Printf("Found %d processes", len(v))
for i, p := range v {
log.Printf("Process %d: %T", i, p)
if processMap, ok := p.(map[string]interface{}); ok {
log.Printf(" Name: %v", processMap["name"])
log.Printf(" Command: %v", processMap["command"])
log.Printf(" Status: %v", processMap["status"])
}
}
case map[string]interface{}:
log.Printf("Process list is a map with %d entries", len(v))
for k, val := range v {
log.Printf(" %s: %T", k, val)
}
default:
log.Printf("Process list is of unexpected type: %T", processList)
}
log.Println("📜 Getting process logs...")
// Get process logs
logResult, err := client.GetProcessLogs("example-process", 10)
if err != nil {
return fmt.Errorf("failed to get process logs: %w", err)
}
log.Printf("Process logs: success=%v, message=%s", logResult.Success, logResult.Message)
log.Printf("Logs:\n%s", logResult.Logs)
log.Println("🔄 Restarting process...")
// Restart the process
restartResult, err := client.RestartProcess("example-process")
if err != nil {
return fmt.Errorf("failed to restart process: %w", err)
}
log.Printf("Restart result: success=%v, message=%s, PID=%d", restartResult.Success, restartResult.Message, restartResult.PID)
// Wait a bit for the process to restart
time.Sleep(500 * time.Millisecond)
// Get the process status after restart
status, err = client.GetProcessStatus("example-process", "json")
if err != nil {
return fmt.Errorf("failed to get process status after restart: %w", err)
}
log.Println("Process status after restart:")
printProcessStatus(status.(interfaces.ProcessStatus))
log.Println("⏹️ Stopping process...")
// Stop the process
stopResult, err := client.StopProcess("example-process")
if err != nil {
return fmt.Errorf("failed to stop process: %w", err)
}
log.Printf("Stop result: success=%v, message=%s", stopResult.Success, stopResult.Message)
// Wait a bit for the process to stop
time.Sleep(500 * time.Millisecond)
// Get the process status after stop
status, err = client.GetProcessStatus("example-process", "json")
if err != nil {
return fmt.Errorf("failed to get process status after stop: %w", err)
}
log.Println("Process status after stop:")
printProcessStatus(status.(interfaces.ProcessStatus))
log.Println("🗑️ Deleting process...")
// Delete the process
deleteResult, err := client.DeleteProcess("example-process")
if err != nil {
return fmt.Errorf("failed to delete process: %w", err)
}
log.Printf("Delete result: success=%v, message=%s", deleteResult.Success, deleteResult.Message)
// Try to get the process status after delete (should fail)
_, err = client.GetProcessStatus("example-process", "json")
if err != nil {
log.Printf("Expected error after deletion: %v", err)
} else {
return fmt.Errorf("process still exists after deletion")
}
log.Println("✅ Example completed successfully!")
return nil
}
// printProcessStatus prints the status of a process
func printProcessStatus(status interfaces.ProcessStatus) {
log.Printf("Process: %s", status.Name)
log.Printf(" Command: %s", status.Command)
log.Printf(" Status: %s", status.Status)
log.Printf(" PID: %d", status.PID)
if status.CPUPercent > 0 {
log.Printf(" CPU: %.2f%%", status.CPUPercent)
}
if status.MemoryMB > 0 {
log.Printf(" Memory: %.2f MB", status.MemoryMB)
}
if !status.StartTime.IsZero() {
log.Printf(" Started: %s", status.StartTime.Format(time.RFC3339))
}
}

View File

@ -1,75 +0,0 @@
package main
import (
"log"
"os"
"os/signal"
"syscall"
"time"
"git.ourworld.tf/herocode/heroagent/pkg/processmanager/interfaces/openrpc"
)
func main() {
log.Println("Starting Process Manager OpenRPC Example")
// Use /tmp directory for the socket as it's more reliable for Unix sockets
// Define the socket path
socketPath := "/tmp/process-manager.sock"
// Remove the socket file if it already exists
if _, err := os.Stat(socketPath); err == nil {
if err := os.Remove(socketPath); err != nil {
log.Fatalf("Failed to remove existing socket file: %v", err)
}
}
log.Printf("Using socket path: %s", socketPath)
// Create a mock process manager
mockPM := NewMockProcessManager()
// Create and start the server
server, err := openrpc.NewServer(mockPM, socketPath)
if err != nil {
log.Fatalf("Failed to create server: %v", err)
}
// Start the server
err = server.Start()
if err != nil {
log.Fatalf("Failed to start server: %v", err)
}
log.Println("Server started successfully")
// Set up signal handling for graceful shutdown
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
// Wait a bit for the server to start
time.Sleep(1 * time.Second)
// Run the client example in a goroutine
errChan := make(chan error, 1)
go func() {
err := RunClientExample(socketPath, mockPM.GetSecret())
errChan <- err
}()
// Wait for the client to finish or for a signal
select {
case err := <-errChan:
if err != nil {
log.Printf("Client example failed: %v", err)
}
case sig := <-sigChan:
log.Printf("Received signal: %v", sig)
}
// Stop the server
log.Println("Stopping server...")
err = server.Stop()
if err != nil {
log.Printf("Failed to stop server: %v", err)
}
log.Println("Example completed")
}

View File

@ -1,195 +0,0 @@
package main
import (
"fmt"
"strings"
"sync"
"time"
"git.ourworld.tf/herocode/heroagent/pkg/processmanager"
"git.ourworld.tf/herocode/heroagent/pkg/processmanager/interfaces"
)
// MockProcessManager implements the interfaces.ProcessManagerInterface for testing purposes
type MockProcessManager struct {
processes map[string]*processmanager.ProcessInfo
logs map[string][]string
mutex sync.RWMutex
secret string
}
// Ensure MockProcessManager implements interfaces.ProcessManagerInterface
var _ interfaces.ProcessManagerInterface = (*MockProcessManager)(nil)
// NewMockProcessManager creates a new mock process manager
func NewMockProcessManager() *MockProcessManager {
return &MockProcessManager{
processes: make(map[string]*processmanager.ProcessInfo),
logs: make(map[string][]string),
secret: "mock-secret",
}
}
// StartProcess starts a new process
func (m *MockProcessManager) StartProcess(name, command string, logEnabled bool, deadline int, cron, jobID string) error {
m.mutex.Lock()
defer m.mutex.Unlock()
if _, exists := m.processes[name]; exists {
return fmt.Errorf("process %s already exists", name)
}
process := &processmanager.ProcessInfo{
Name: name,
Command: command,
PID: 12345, // Mock PID
Status: processmanager.ProcessStatusRunning,
CPUPercent: 0.5,
MemoryMB: 10.0,
StartTime: time.Now(),
LogEnabled: logEnabled,
Cron: cron,
JobID: jobID,
Deadline: deadline,
}
m.processes[name] = process
m.logs[name] = []string{
fmt.Sprintf("[%s] Process started: %s", time.Now().Format(time.RFC3339), command),
}
return nil
}
// StopProcess stops a running process
func (m *MockProcessManager) StopProcess(name string) error {
m.mutex.Lock()
defer m.mutex.Unlock()
process, exists := m.processes[name]
if !exists {
return fmt.Errorf("process %s does not exist", name)
}
if process.Status != processmanager.ProcessStatusRunning {
return fmt.Errorf("process %s is not running", name)
}
process.Status = processmanager.ProcessStatusStopped
m.logs[name] = append(m.logs[name], fmt.Sprintf("[%s] Process stopped", time.Now().Format(time.RFC3339)))
return nil
}
// RestartProcess restarts a process
func (m *MockProcessManager) RestartProcess(name string) error {
m.mutex.Lock()
defer m.mutex.Unlock()
process, exists := m.processes[name]
if !exists {
return fmt.Errorf("process %s does not exist", name)
}
process.Status = processmanager.ProcessStatusRunning
process.StartTime = time.Now()
m.logs[name] = append(m.logs[name], fmt.Sprintf("[%s] Process restarted", time.Now().Format(time.RFC3339)))
return nil
}
// DeleteProcess deletes a process
func (m *MockProcessManager) DeleteProcess(name string) error {
m.mutex.Lock()
defer m.mutex.Unlock()
if _, exists := m.processes[name]; !exists {
return fmt.Errorf("process %s does not exist", name)
}
delete(m.processes, name)
delete(m.logs, name)
return nil
}
// GetProcessStatus gets the status of a process
func (m *MockProcessManager) GetProcessStatus(name string) (*processmanager.ProcessInfo, error) {
m.mutex.RLock()
defer m.mutex.RUnlock()
process, exists := m.processes[name]
if !exists {
return nil, fmt.Errorf("process %s does not exist", name)
}
// Return a copy to avoid race conditions
return &processmanager.ProcessInfo{
Name: process.Name,
Command: process.Command,
PID: process.PID,
Status: process.Status,
CPUPercent: process.CPUPercent,
MemoryMB: process.MemoryMB,
StartTime: process.StartTime,
LogEnabled: process.LogEnabled,
Cron: process.Cron,
JobID: process.JobID,
Deadline: process.Deadline,
}, nil
}
// ListProcesses lists all processes
func (m *MockProcessManager) ListProcesses() []*processmanager.ProcessInfo {
m.mutex.RLock()
defer m.mutex.RUnlock()
processes := make([]*processmanager.ProcessInfo, 0, len(m.processes))
for _, process := range m.processes {
// Create a copy to avoid race conditions
processes = append(processes, &processmanager.ProcessInfo{
Name: process.Name,
Command: process.Command,
PID: process.PID,
Status: process.Status,
CPUPercent: process.CPUPercent,
MemoryMB: process.MemoryMB,
StartTime: process.StartTime,
LogEnabled: process.LogEnabled,
Cron: process.Cron,
JobID: process.JobID,
Deadline: process.Deadline,
})
}
return processes
}
// GetProcessLogs gets the logs for a process
func (m *MockProcessManager) GetProcessLogs(name string, maxLines int) (string, error) {
m.mutex.RLock()
defer m.mutex.RUnlock()
logs, exists := m.logs[name]
if !exists {
return "", fmt.Errorf("logs for process %s do not exist", name)
}
if maxLines <= 0 || maxLines > len(logs) {
return strings.Join(logs, "\n"), nil
}
return strings.Join(logs[len(logs)-maxLines:], "\n"), nil
}
// SetLogsBasePath sets the base path for logs (mock implementation does nothing)
func (m *MockProcessManager) SetLogsBasePath(path string) {
// No-op for mock
}
// GetSecret returns the authentication secret
func (m *MockProcessManager) GetSecret() string {
return m.secret
}

View File

@ -1,242 +0,0 @@
package openrpc
import (
"encoding/json"
"fmt"
"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
type Client struct {
client.BaseClient
secret string
}
// NewClient creates a new client for the process manager API
func NewClient(socketPath, secret string) *Client {
return &Client{
BaseClient: *client.NewClient(socketPath, secret),
}
}
// StartProcess starts a new process with the given name and command
func (c *Client) StartProcess(name, command string, logEnabled bool, deadline int, cron, jobID string) (interfaces.ProcessStartResult, error) {
params := map[string]interface{}{
"name": name,
"command": command,
"log_enabled": logEnabled,
"deadline": deadline,
"cron": cron,
"job_id": jobID,
}
paramsJSON, err := json.Marshal(params)
if err != nil {
return interfaces.ProcessStartResult{}, fmt.Errorf("failed to marshal parameters: %w", err)
}
result, err := c.Request("process.start", paramsJSON, "")
if err != nil {
return interfaces.ProcessStartResult{}, fmt.Errorf("failed to start process: %w", err)
}
// Convert result to ProcessStartResult
resultJSON, err := json.Marshal(result)
if err != nil {
return interfaces.ProcessStartResult{}, fmt.Errorf("failed to marshal result: %w", err)
}
var startResult interfaces.ProcessStartResult
if err := json.Unmarshal(resultJSON, &startResult); err != nil {
return interfaces.ProcessStartResult{}, fmt.Errorf("failed to unmarshal process start result: %w", err)
}
return startResult, nil
}
// StopProcess stops a running process
func (c *Client) StopProcess(name string) (interfaces.ProcessStopResult, error) {
params := map[string]string{
"name": name,
}
paramsJSON, err := json.Marshal(params)
if err != nil {
return interfaces.ProcessStopResult{}, fmt.Errorf("failed to marshal parameters: %w", err)
}
result, err := c.Request("process.stop", paramsJSON, "")
if err != nil {
return interfaces.ProcessStopResult{}, fmt.Errorf("failed to stop process: %w", err)
}
// Convert result to ProcessStopResult
resultJSON, err := json.Marshal(result)
if err != nil {
return interfaces.ProcessStopResult{}, fmt.Errorf("failed to marshal result: %w", err)
}
var stopResult interfaces.ProcessStopResult
if err := json.Unmarshal(resultJSON, &stopResult); err != nil {
return interfaces.ProcessStopResult{}, fmt.Errorf("failed to unmarshal process stop result: %w", err)
}
return stopResult, nil
}
// RestartProcess restarts a process
func (c *Client) RestartProcess(name string) (interfaces.ProcessRestartResult, error) {
params := map[string]string{
"name": name,
}
paramsJSON, err := json.Marshal(params)
if err != nil {
return interfaces.ProcessRestartResult{}, fmt.Errorf("failed to marshal parameters: %w", err)
}
result, err := c.Request("process.restart", paramsJSON, "")
if err != nil {
return interfaces.ProcessRestartResult{}, fmt.Errorf("failed to restart process: %w", err)
}
// Convert result to ProcessRestartResult
resultJSON, err := json.Marshal(result)
if err != nil {
return interfaces.ProcessRestartResult{}, fmt.Errorf("failed to marshal result: %w", err)
}
var restartResult interfaces.ProcessRestartResult
if err := json.Unmarshal(resultJSON, &restartResult); err != nil {
return interfaces.ProcessRestartResult{}, fmt.Errorf("failed to unmarshal process restart result: %w", err)
}
return restartResult, nil
}
// DeleteProcess deletes a process from the manager
func (c *Client) DeleteProcess(name string) (interfaces.ProcessDeleteResult, error) {
params := map[string]string{
"name": name,
}
paramsJSON, err := json.Marshal(params)
if err != nil {
return interfaces.ProcessDeleteResult{}, fmt.Errorf("failed to marshal parameters: %w", err)
}
result, err := c.Request("process.delete", paramsJSON, "")
if err != nil {
return interfaces.ProcessDeleteResult{}, fmt.Errorf("failed to delete process: %w", err)
}
// Convert result to ProcessDeleteResult
resultJSON, err := json.Marshal(result)
if err != nil {
return interfaces.ProcessDeleteResult{}, fmt.Errorf("failed to marshal result: %w", err)
}
var deleteResult interfaces.ProcessDeleteResult
if err := json.Unmarshal(resultJSON, &deleteResult); err != nil {
return interfaces.ProcessDeleteResult{}, fmt.Errorf("failed to unmarshal process delete result: %w", err)
}
return deleteResult, nil
}
// GetProcessStatus gets the status of a process
func (c *Client) GetProcessStatus(name string, format string) (interface{}, error) {
params := map[string]string{
"name": name,
"format": format,
}
paramsJSON, err := json.Marshal(params)
if err != nil {
return nil, fmt.Errorf("failed to marshal parameters: %w", err)
}
result, err := c.Request("process.status", paramsJSON, "")
if err != nil {
return nil, fmt.Errorf("failed to get process status: %w", err)
}
if format == "text" {
// For text format, return the raw result
return result, nil
}
// For JSON format, convert to ProcessStatus
resultJSON, err := json.Marshal(result)
if err != nil {
return nil, fmt.Errorf("failed to marshal result: %w", err)
}
var statusResult interfaces.ProcessStatus
if err := json.Unmarshal(resultJSON, &statusResult); err != nil {
return nil, fmt.Errorf("failed to unmarshal process status result: %w", err)
}
return statusResult, nil
}
// ListProcesses lists all processes
func (c *Client) ListProcesses(format string) (interface{}, error) {
params := map[string]string{
"format": format,
}
paramsJSON, err := json.Marshal(params)
if err != nil {
return nil, fmt.Errorf("failed to marshal parameters: %w", err)
}
result, err := c.Request("process.list", paramsJSON, "")
if err != nil {
return nil, fmt.Errorf("failed to list processes: %w", err)
}
if format == "text" {
// For text format, return the raw result
return result, nil
}
// For JSON format, convert to []ProcessStatus
resultJSON, err := json.Marshal(result)
if err != nil {
return nil, fmt.Errorf("failed to marshal result: %w", err)
}
var listResult []interfaces.ProcessStatus
if err := json.Unmarshal(resultJSON, &listResult); err != nil {
return nil, fmt.Errorf("failed to unmarshal process list result: %w", err)
}
return listResult, nil
}
// GetProcessLogs gets logs for a process
func (c *Client) GetProcessLogs(name string, lines int) (interfaces.ProcessLogResult, error) {
params := map[string]interface{}{
"name": name,
"lines": lines,
}
paramsJSON, err := json.Marshal(params)
if err != nil {
return interfaces.ProcessLogResult{}, fmt.Errorf("failed to marshal parameters: %w", err)
}
result, err := c.Request("process.log", paramsJSON, "")
if err != nil {
return interfaces.ProcessLogResult{}, fmt.Errorf("failed to get process logs: %w", err)
}
// Convert result to ProcessLogResult
resultJSON, err := json.Marshal(result)
if err != nil {
return interfaces.ProcessLogResult{}, fmt.Errorf("failed to marshal result: %w", err)
}
var logResult interfaces.ProcessLogResult
if err := json.Unmarshal(resultJSON, &logResult); err != nil {
return interfaces.ProcessLogResult{}, fmt.Errorf("failed to unmarshal process log result: %w", err)
}
return logResult, nil
}

View File

@ -1,269 +0,0 @@
package openrpc
import (
"encoding/json"
"fmt"
"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
type Handler struct {
processManager interfaces.ProcessManagerInterface
}
// NewHandler creates a new RPC handler for process manager operations
func NewHandler(processManager interfaces.ProcessManagerInterface) *Handler {
return &Handler{
processManager: processManager,
}
}
// GetHandlers returns a map of RPC handlers for the OpenRPC manager
func (h *Handler) GetHandlers() map[string]openrpcmanager.RPCHandler {
return map[string]openrpcmanager.RPCHandler{
"process.start": h.handleProcessStart,
"process.stop": h.handleProcessStop,
"process.restart": h.handleProcessRestart,
"process.delete": h.handleProcessDelete,
"process.status": h.handleProcessStatus,
"process.list": h.handleProcessList,
"process.log": h.handleProcessLog,
}
}
// handleProcessStart handles the process.start method
func (h *Handler) handleProcessStart(params json.RawMessage) (interface{}, error) {
var request struct {
Name string `json:"name"`
Command string `json:"command"`
LogEnabled bool `json:"log_enabled"`
Deadline int `json:"deadline"`
Cron string `json:"cron"`
JobID string `json:"job_id"`
}
if err := json.Unmarshal(params, &request); err != nil {
return nil, fmt.Errorf("invalid parameters: %w", err)
}
err := h.processManager.StartProcess(
request.Name,
request.Command,
request.LogEnabled,
request.Deadline,
request.Cron,
request.JobID,
)
result := interfaces.ProcessStartResult{
Success: err == nil,
Message: "",
}
if err != nil {
result.Message = err.Error()
return result, nil
}
// Get the process info to return the PID
procInfo, err := h.processManager.GetProcessStatus(request.Name)
if err != nil {
result.Message = fmt.Sprintf("Process started but failed to get status: %v", err)
return result, nil
}
result.PID = procInfo.PID
result.Message = fmt.Sprintf("Process '%s' started successfully with PID %d", request.Name, procInfo.PID)
return result, nil
}
// handleProcessStop handles the process.stop method
func (h *Handler) handleProcessStop(params json.RawMessage) (interface{}, error) {
var request struct {
Name string `json:"name"`
}
if err := json.Unmarshal(params, &request); err != nil {
return nil, fmt.Errorf("invalid parameters: %w", err)
}
err := h.processManager.StopProcess(request.Name)
result := interfaces.ProcessStopResult{
Success: err == nil,
Message: "",
}
if err != nil {
result.Message = err.Error()
} else {
result.Message = fmt.Sprintf("Process '%s' stopped successfully", request.Name)
}
return result, nil
}
// handleProcessRestart handles the process.restart method
func (h *Handler) handleProcessRestart(params json.RawMessage) (interface{}, error) {
var request struct {
Name string `json:"name"`
}
if err := json.Unmarshal(params, &request); err != nil {
return nil, fmt.Errorf("invalid parameters: %w", err)
}
err := h.processManager.RestartProcess(request.Name)
result := interfaces.ProcessRestartResult{
Success: err == nil,
Message: "",
}
if err != nil {
result.Message = err.Error()
return result, nil
}
// Get the process info to return the PID
procInfo, err := h.processManager.GetProcessStatus(request.Name)
if err != nil {
result.Message = fmt.Sprintf("Process restarted but failed to get status: %v", err)
return result, nil
}
result.PID = procInfo.PID
result.Message = fmt.Sprintf("Process '%s' restarted successfully with PID %d", request.Name, procInfo.PID)
return result, nil
}
// handleProcessDelete handles the process.delete method
func (h *Handler) handleProcessDelete(params json.RawMessage) (interface{}, error) {
var request struct {
Name string `json:"name"`
}
if err := json.Unmarshal(params, &request); err != nil {
return nil, fmt.Errorf("invalid parameters: %w", err)
}
err := h.processManager.DeleteProcess(request.Name)
result := interfaces.ProcessDeleteResult{
Success: err == nil,
Message: "",
}
if err != nil {
result.Message = err.Error()
} else {
result.Message = fmt.Sprintf("Process '%s' deleted successfully", request.Name)
}
return result, nil
}
// handleProcessStatus handles the process.status method
func (h *Handler) handleProcessStatus(params json.RawMessage) (interface{}, error) {
var request struct {
Name string `json:"name"`
Format string `json:"format"`
}
if err := json.Unmarshal(params, &request); err != nil {
return nil, fmt.Errorf("invalid parameters: %w", err)
}
procInfo, err := h.processManager.GetProcessStatus(request.Name)
if err != nil {
return nil, fmt.Errorf("failed to get process status: %w", err)
}
if request.Format == "text" {
// Format as text using the processmanager's FormatProcessInfo function
textResult, err := processmanager.FormatProcessInfo(procInfo, "text")
if err != nil {
return nil, fmt.Errorf("failed to format process info: %w", err)
}
return map[string]interface{}{
"text": textResult,
}, nil
}
// Default to JSON format
return convertProcessInfoToStatus(procInfo), nil
}
// handleProcessList handles the process.list method
func (h *Handler) handleProcessList(params json.RawMessage) (interface{}, error) {
var request struct {
Format string `json:"format"`
}
if err := json.Unmarshal(params, &request); err != nil {
return nil, fmt.Errorf("invalid parameters: %w", err)
}
processes := h.processManager.ListProcesses()
if request.Format == "text" {
// Format as text using the processmanager's FormatProcessList function
textResult, err := processmanager.FormatProcessList(processes, "text")
if err != nil {
return nil, fmt.Errorf("failed to format process list: %w", err)
}
return map[string]interface{}{
"text": textResult,
}, nil
}
// Default to JSON format
result := make([]interfaces.ProcessStatus, 0, len(processes))
for _, proc := range processes {
result = append(result, convertProcessInfoToStatus(proc))
}
return result, nil
}
// handleProcessLog handles the process.log method
func (h *Handler) handleProcessLog(params json.RawMessage) (interface{}, error) {
var request struct {
Name string `json:"name"`
Lines int `json:"lines"`
}
if err := json.Unmarshal(params, &request); err != nil {
return nil, fmt.Errorf("invalid parameters: %w", err)
}
logs, err := h.processManager.GetProcessLogs(request.Name, request.Lines)
result := interfaces.ProcessLogResult{
Success: err == nil,
Message: "",
Logs: logs,
}
if err != nil {
result.Message = err.Error()
result.Logs = ""
} else {
result.Message = fmt.Sprintf("Retrieved %d lines of logs for process '%s'", request.Lines, request.Name)
}
return result, nil
}
// convertProcessInfoToStatus converts a ProcessInfo to a ProcessStatus
func convertProcessInfoToStatus(info *processmanager.ProcessInfo) interfaces.ProcessStatus {
return interfaces.ProcessStatus{
Name: info.Name,
Command: info.Command,
PID: info.PID,
Status: string(info.Status),
CPUPercent: info.CPUPercent,
MemoryMB: info.MemoryMB,
StartTime: info.StartTime,
LogEnabled: info.LogEnabled,
Cron: info.Cron,
JobID: info.JobID,
Deadline: info.Deadline,
Error: info.Error,
}
}

View File

@ -1,130 +0,0 @@
package openrpc
import (
"os"
"path/filepath"
"testing"
"time"
"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"
)
func TestProcessManagerRPC(t *testing.T) {
// Create a temporary directory for the socket
tempDir, err := os.MkdirTemp("", "processmanager-rpc-test")
require.NoError(t, err)
defer os.RemoveAll(tempDir)
// Create a socket path
socketPath := filepath.Join(tempDir, "process-manager.sock")
// Create a process manager
pm := processmanager.NewProcessManager()
pm.SetLogsBasePath(filepath.Join(tempDir, "logs"))
// Create and start the server
server, err := NewServer(pm, socketPath)
require.NoError(t, err)
// Start the server in a goroutine
go func() {
err := server.Start()
if err != nil {
t.Logf("Error starting server: %v", err)
}
}()
// Wait for the server to start
time.Sleep(100 * time.Millisecond)
// Create a client
client := NewClient(socketPath, "")
// Test process start
t.Run("StartProcess", func(t *testing.T) {
result, err := client.StartProcess("test-process", "echo 'Hello, World!'", true, 0, "", "")
require.NoError(t, err)
assert.True(t, result.Success)
assert.NotEmpty(t, result.Message)
assert.NotZero(t, result.PID)
})
// Test process status
t.Run("GetProcessStatus", func(t *testing.T) {
status, err := client.GetProcessStatus("test-process", "json")
require.NoError(t, err)
processStatus, ok := status.(interfaces.ProcessStatus)
require.True(t, ok)
assert.Equal(t, "test-process", processStatus.Name)
assert.Equal(t, "echo 'Hello, World!'", processStatus.Command)
})
// Test process list
t.Run("ListProcesses", func(t *testing.T) {
processList, err := client.ListProcesses("json")
require.NoError(t, err)
processes, ok := processList.([]interfaces.ProcessStatus)
require.True(t, ok)
assert.NotEmpty(t, processes)
// Find our test process
found := false
for _, proc := range processes {
if proc.Name == "test-process" {
found = true
break
}
}
assert.True(t, found)
})
// Test process logs
t.Run("GetProcessLogs", func(t *testing.T) {
// Wait a bit for logs to be generated
time.Sleep(100 * time.Millisecond)
logs, err := client.GetProcessLogs("test-process", 10)
require.NoError(t, err)
assert.True(t, logs.Success)
})
// Test process restart
t.Run("RestartProcess", func(t *testing.T) {
result, err := client.RestartProcess("test-process")
require.NoError(t, err)
assert.True(t, result.Success)
assert.NotEmpty(t, result.Message)
})
// Test process stop
t.Run("StopProcess", func(t *testing.T) {
result, err := client.StopProcess("test-process")
require.NoError(t, err)
assert.True(t, result.Success)
assert.NotEmpty(t, result.Message)
})
// Test process delete
t.Run("DeleteProcess", func(t *testing.T) {
result, err := client.DeleteProcess("test-process")
require.NoError(t, err)
assert.True(t, result.Success)
assert.NotEmpty(t, result.Message)
})
// Stop the server
err = server.Stop()
require.NoError(t, err)
}
// TestProcessManagerRPCWithMock tests the RPC interface with a mock process manager
func TestProcessManagerRPCWithMock(t *testing.T) {
// This test would use a mock implementation of the ProcessManagerInterface
// to test the RPC layer without actually starting real processes
t.Skip("Mock implementation test to be added")
}

View File

@ -1,32 +0,0 @@
package openrpc
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"runtime"
"git.ourworld.tf/herocode/heroagent/pkg/openrpcmanager"
)
// LoadSchema loads the OpenRPC schema from the embedded JSON file
func LoadSchema() (openrpcmanager.OpenRPCSchema, error) {
// Get the absolute path to the schema.json file
_, filename, _, _ := runtime.Caller(0)
schemaPath := filepath.Join(filepath.Dir(filename), "schema.json")
// Read the schema file
schemaBytes, err := os.ReadFile(schemaPath)
if err != nil {
return openrpcmanager.OpenRPCSchema{}, fmt.Errorf("failed to read schema file: %w", err)
}
// Unmarshal the schema
var schema openrpcmanager.OpenRPCSchema
if err := json.Unmarshal(schemaBytes, &schema); err != nil {
return openrpcmanager.OpenRPCSchema{}, fmt.Errorf("failed to unmarshal schema: %w", err)
}
return schema, nil
}

View File

@ -1,333 +0,0 @@
{
"openrpc": "1.2.6",
"info": {
"title": "Process Manager API",
"version": "1.0.0",
"description": "API for managing and monitoring processes"
},
"methods": [
{
"name": "process.start",
"description": "Start a new process with the given name and command",
"params": [
{
"name": "name",
"description": "Name of the process",
"schema": {
"type": "string"
}
},
{
"name": "command",
"description": "Command to execute",
"schema": {
"type": "string"
}
},
{
"name": "log_enabled",
"description": "Whether to enable logging for the process",
"schema": {
"type": "boolean"
}
},
{
"name": "deadline",
"description": "Deadline in seconds after which the process will be automatically stopped (0 for no deadline)",
"schema": {
"type": "integer"
}
},
{
"name": "cron",
"description": "Cron expression for scheduled execution",
"schema": {
"type": "string"
}
},
{
"name": "job_id",
"description": "Optional job ID for tracking",
"schema": {
"type": "string"
}
}
],
"result": {
"name": "result",
"description": "Process start result",
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"message": {
"type": "string"
},
"pid": {
"type": "integer"
}
}
}
}
},
{
"name": "process.stop",
"description": "Stop a running process",
"params": [
{
"name": "name",
"description": "Name of the process to stop",
"schema": {
"type": "string"
}
}
],
"result": {
"name": "result",
"description": "Process stop result",
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"message": {
"type": "string"
}
}
}
}
},
{
"name": "process.restart",
"description": "Restart a process",
"params": [
{
"name": "name",
"description": "Name of the process to restart",
"schema": {
"type": "string"
}
}
],
"result": {
"name": "result",
"description": "Process restart result",
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"message": {
"type": "string"
},
"pid": {
"type": "integer"
}
}
}
}
},
{
"name": "process.delete",
"description": "Delete a process from the manager",
"params": [
{
"name": "name",
"description": "Name of the process to delete",
"schema": {
"type": "string"
}
}
],
"result": {
"name": "result",
"description": "Process delete result",
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"message": {
"type": "string"
}
}
}
}
},
{
"name": "process.status",
"description": "Get the status of a process",
"params": [
{
"name": "name",
"description": "Name of the process",
"schema": {
"type": "string"
}
},
{
"name": "format",
"description": "Output format (json or text)",
"schema": {
"type": "string",
"enum": ["json", "text"]
}
}
],
"result": {
"name": "result",
"description": "Process status information",
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"command": {
"type": "string"
},
"pid": {
"type": "integer"
},
"status": {
"type": "string",
"enum": ["running", "stopped", "failed", "completed"]
},
"cpu_percent": {
"type": "number"
},
"memory_mb": {
"type": "number"
},
"start_time": {
"type": "string",
"format": "date-time"
},
"log_enabled": {
"type": "boolean"
},
"cron": {
"type": "string"
},
"job_id": {
"type": "string"
},
"deadline": {
"type": "integer"
},
"error": {
"type": "string"
}
}
}
}
},
{
"name": "process.list",
"description": "List all processes",
"params": [
{
"name": "format",
"description": "Output format (json or text)",
"schema": {
"type": "string",
"enum": ["json", "text"]
}
}
],
"result": {
"name": "result",
"description": "List of processes",
"schema": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"command": {
"type": "string"
},
"pid": {
"type": "integer"
},
"status": {
"type": "string",
"enum": ["running", "stopped", "failed", "completed"]
},
"cpu_percent": {
"type": "number"
},
"memory_mb": {
"type": "number"
},
"start_time": {
"type": "string",
"format": "date-time"
},
"log_enabled": {
"type": "boolean"
},
"cron": {
"type": "string"
},
"job_id": {
"type": "string"
},
"deadline": {
"type": "integer"
},
"error": {
"type": "string"
}
}
}
}
}
},
{
"name": "process.log",
"description": "Get logs for a process",
"params": [
{
"name": "name",
"description": "Name of the process",
"schema": {
"type": "string"
}
},
{
"name": "lines",
"description": "Number of log lines to retrieve",
"schema": {
"type": "integer"
}
}
],
"result": {
"name": "result",
"description": "Process logs",
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"message": {
"type": "string"
},
"logs": {
"type": "string"
}
}
}
}
}
]
}

View File

@ -1,100 +0,0 @@
package openrpc
import (
"fmt"
"log"
"os"
"path/filepath"
"git.ourworld.tf/herocode/heroagent/pkg/openrpcmanager"
"git.ourworld.tf/herocode/heroagent/pkg/processmanager/interfaces"
)
// Server represents the Process Manager OpenRPC server
type Server struct {
processManager interfaces.ProcessManagerInterface
socketPath string
openRPCMgr *openrpcmanager.OpenRPCManager
unixServer *openrpcmanager.UnixServer
isRunning bool
}
// NewServer creates a new Process Manager OpenRPC server
func NewServer(processManager interfaces.ProcessManagerInterface, socketPath string) (*Server, error) {
// Ensure the directory for the socket exists
socketDir := filepath.Dir(socketPath)
if err := os.MkdirAll(socketDir, 0755); err != nil {
return nil, fmt.Errorf("failed to create socket directory: %w", err)
}
// Load the OpenRPC schema
schema, err := LoadSchema()
if err != nil {
return nil, fmt.Errorf("failed to load OpenRPC schema: %w", err)
}
// Create a new handler - no authentication now
handler := NewHandler(processManager)
// Create a new OpenRPC manager - using empty string for auth (no authentication)
openRPCMgr, err := openrpcmanager.NewOpenRPCManager(schema, handler.GetHandlers(), "")
if err != nil {
return nil, fmt.Errorf("failed to create OpenRPC manager: %w", err)
}
// Create a new Unix server
unixServer, err := openrpcmanager.NewUnixServer(openRPCMgr, socketPath)
if err != nil {
return nil, fmt.Errorf("failed to create Unix server: %w", err)
}
return &Server{
processManager: processManager,
socketPath: socketPath,
openRPCMgr: openRPCMgr,
unixServer: unixServer,
isRunning: false,
}, nil
}
// Start starts the Process Manager OpenRPC server
func (s *Server) Start() error {
if s.isRunning {
return fmt.Errorf("server is already running")
}
// Start the Unix server
if err := s.unixServer.Start(); err != nil {
return fmt.Errorf("failed to start Unix server: %w", err)
}
s.isRunning = true
log.Printf("Process Manager OpenRPC server started on socket: %s", s.socketPath)
return nil
}
// Stop stops the Process Manager OpenRPC server
func (s *Server) Stop() error {
if !s.isRunning {
return fmt.Errorf("server is not running")
}
// Stop the Unix server
if err := s.unixServer.Stop(); err != nil {
return fmt.Errorf("failed to stop Unix server: %w", err)
}
s.isRunning = false
log.Printf("Process Manager OpenRPC server stopped")
return nil
}
// IsRunning returns whether the server is running
func (s *Server) IsRunning() bool {
return s.isRunning
}
// SocketPath returns the socket path
func (s *Server) SocketPath() string {
return s.socketPath
}

View File

@ -1,80 +0,0 @@
package interfaces
import (
"time"
"git.ourworld.tf/herocode/heroagent/pkg/processmanager"
)
// ProcessManagerInterface defines the interface for process management operations
type ProcessManagerInterface interface {
// StartProcess starts a new process with the given name and command
StartProcess(name, command string, logEnabled bool, deadline int, cron, jobID string) error
// StopProcess stops a running process
StopProcess(name string) error
// RestartProcess restarts a process
RestartProcess(name string) error
// DeleteProcess removes a process from the manager
DeleteProcess(name string) error
// GetProcessStatus returns the status of a process
GetProcessStatus(name string) (*processmanager.ProcessInfo, error)
// ListProcesses returns a list of all processes
ListProcesses() []*processmanager.ProcessInfo
// GetProcessLogs returns the logs for a specific process
GetProcessLogs(name string, lines int) (string, error)
}
// ProcessStartResult represents the result of starting a process
type ProcessStartResult struct {
Success bool `json:"success"`
Message string `json:"message"`
PID int32 `json:"pid"`
}
// ProcessStopResult represents the result of stopping a process
type ProcessStopResult struct {
Success bool `json:"success"`
Message string `json:"message"`
}
// ProcessRestartResult represents the result of restarting a process
type ProcessRestartResult struct {
Success bool `json:"success"`
Message string `json:"message"`
PID int32 `json:"pid"`
}
// ProcessDeleteResult represents the result of deleting a process
type ProcessDeleteResult struct {
Success bool `json:"success"`
Message string `json:"message"`
}
// ProcessLogResult represents the result of getting process logs
type ProcessLogResult struct {
Success bool `json:"success"`
Message string `json:"message"`
Logs string `json:"logs"`
}
// ProcessStatus represents detailed information about a process
type ProcessStatus struct {
Name string `json:"name"`
Command string `json:"command"`
PID int32 `json:"pid"`
Status string `json:"status"`
CPUPercent float64 `json:"cpu_percent"`
MemoryMB float64 `json:"memory_mb"`
StartTime time.Time `json:"start_time"`
LogEnabled bool `json:"log_enabled"`
Cron string `json:"cron,omitempty"`
JobID string `json:"job_id,omitempty"`
Deadline int `json:"deadline,omitempty"`
Error string `json:"error,omitempty"`
}

View File

@ -0,0 +1,90 @@
# Process Manager
The Process Manager (`processmanager`) is a Go package responsible for starting, stopping, monitoring, and managing system processes. It provides a robust way to handle long-running tasks, scheduled jobs (via cron expressions), and general command execution with logging capabilities.
It can be interacted with via `heroscript` commands, typically through a telnet interface to a `ProcessManagerHandler` server.
## Features
- **Start and Stop Processes**: Launch new processes and terminate existing ones.
- **Restart Processes**: Convenience function to stop, delete, and restart a process.
- **Delete Processes**: Remove a process definition and its associated logs.
- **Process Monitoring**: Tracks process status (running, stopped, failed, completed), PID, CPU, and memory usage.
- **Logging**: Captures stdout and stderr for each managed process. Logs are stored in a configurable base directory, with each process having its own subdirectory.
- **Deadline Management**: Processes can be started with a deadline, after which they will be automatically stopped.
- **Cron Scheduling**: Processes can be scheduled to run based on cron expressions (though the actual scheduling logic might be external and trigger `process.start`).
- **Heroscript Integration**: Exposes functionality through `heroscript` actions.
## Direct Usage in Go (Conceptual)
While primarily designed for `heroscript` interaction, the core `ProcessManager` can be used directly in Go applications:
```go
package main
import (
"fmt"
"time"
"git.ourworld.tf/herocode/heroagent/pkg/system/processmanager"
)
func main() {
pm := processmanager.NewProcessManager()
// Configure logs path (optional, defaults to a temp directory)
// pm.SetLogsBasePath("./my_process_logs")
processName := "my_go_task"
command := "sleep 10 && echo 'Task finished'"
fmt.Printf("Starting process: %s\n", processName)
err := pm.StartProcess(processName, command, true, 0, "", "") // name, command, logEnabled, deadline, cron, jobID
if err != nil {
fmt.Printf("Error starting process: %v\n", err)
return
}
fmt.Printf("Process '%s' started. Waiting for it to complete or monitoring...\n", processName)
// Monitor status (example)
for i := 0; i < 12; i++ {
time.Sleep(1 * time.Second)
status, err := pm.GetProcessStatus(processName)
if err != nil {
fmt.Printf("Error getting status: %v\n", err)
break
}
fmt.Printf("Status of '%s': %s (PID: %d, CPU: %.2f%%, Mem: %.2fMB)\n",
status.Name, status.Status, status.PID, status.CPUPercent, status.MemoryMB)
if status.Status == processmanager.ProcessStatusCompleted || status.Status == processmanager.ProcessStatusFailed {
fmt.Printf("Process '%s' ended with status: %s\n", processName, status.Status)
if status.Error != "" {
fmt.Printf("Error: %s\n", status.Error)
}
break
}
}
// Retrieve logs
logs, err := pm.GetProcessLogs(processName, 100)
if err != nil {
fmt.Printf("Error getting logs: %v\n", err)
} else {
fmt.Printf("\n--- Logs for %s ---\n%s\n-------------------\n", processName, logs)
}
// Stop (if still running) and delete
fmt.Printf("Stopping process: %s\n", processName)
pm.StopProcess(processName) // Ignoring error for simplicity if already stopped
fmt.Printf("Deleting process: %s\n", processName)
err = pm.DeleteProcess(processName)
if err != nil {
fmt.Printf("Error deleting process: %v\n", err)
}
fmt.Println("Done.")
}
```

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 844 B

After

Width:  |  Height:  |  Size: 844 B

Some files were not shown because too many files have changed in this diff Show More