This commit is contained in:
2025-05-23 16:10:49 +04:00
parent 3f01074e3f
commit 29d0d25a3b
133 changed files with 346 additions and 168 deletions

View File

@@ -0,0 +1,75 @@
package main
import (
"flag"
"fmt"
"os"
"os/signal"
"syscall"
"git.ourworld.tf/herocode/heroagent/pkg/handlerfactory/herohandler"
)
func main() {
// Parse command line flags
socketPath := flag.String("socket", "/tmp/hero.sock", "Unix socket path")
tcpAddress := flag.String("tcp", "localhost:8023", "TCP address")
useUnixSocket := flag.Bool("unix", true, "Use Unix socket")
useTCP := flag.Bool("tcp-enable", false, "Use TCP")
flag.Parse()
// Initialize the hero handler
err := herohandler.Init()
if err != nil {
fmt.Printf("Failed to initialize hero handler: %v\n", err)
os.Exit(1)
}
// Get the default instance
handler := herohandler.DefaultInstance
// The fake handler is already registered in the Init() function
fmt.Println("Using pre-registered fake handler")
// Start the server
if *useUnixSocket || *useTCP {
fmt.Printf("Starting telnet server\n")
var socketPathStr, tcpAddressStr string
if *useUnixSocket {
socketPathStr = *socketPath
fmt.Printf("Unix socket: %s\n", socketPathStr)
}
if *useTCP {
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)
os.Exit(1)
}
}
// Print available commands
factory := handler.GetFactory()
actions := factory.GetSupportedActions()
fmt.Println("\nAvailable commands:")
for actor, commands := range actions {
fmt.Printf("Actor: %s\n", actor)
for _, command := range commands {
fmt.Printf(" !!%s.%s\n", actor, command)
}
}
fmt.Println("\nServer is running. Press Ctrl+C to stop.")
// Wait for interrupt signal
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
<-sigCh
fmt.Println("\nShutting down...")
handler.StopTelnet()
fmt.Println("Server stopped")
}

View File

@@ -0,0 +1 @@
heroexecute

View File

@@ -0,0 +1,165 @@
package main
import (
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"strings"
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/playbook"
)
func main() {
// Define command line flags
parseCmd := flag.NewFlagSet("parse", flag.ExitOnError)
parseFile := parseCmd.String("file", "", "Path to heroscript file to parse")
parseText := parseCmd.String("text", "", "Heroscript text to parse")
parsePriority := parseCmd.Int("priority", 10, "Default priority for actions")
executeCmd := flag.NewFlagSet("execute", flag.ExitOnError)
executeFile := executeCmd.String("file", "", "Path to heroscript file to execute")
executeText := executeCmd.String("text", "", "Heroscript text to execute")
executePriority := executeCmd.Int("priority", 10, "Default priority for actions")
executeActor := executeCmd.String("actor", "", "Only execute actions for this actor")
executeAction := executeCmd.String("action", "", "Only execute actions with this name")
// Check if a subcommand is provided
if len(os.Args) < 2 {
fmt.Println("Expected 'parse' or 'execute' subcommand")
os.Exit(1)
}
// Parse the subcommand
switch os.Args[1] {
case "parse":
parseCmd.Parse(os.Args[2:])
handleParseCommand(*parseFile, *parseText, *parsePriority)
case "execute":
executeCmd.Parse(os.Args[2:])
handleExecuteCommand(*executeFile, *executeText, *executePriority, *executeActor, *executeAction)
default:
fmt.Println("Expected 'parse' or 'execute' subcommand")
os.Exit(1)
}
}
func handleParseCommand(file, text string, priority int) {
var pb *playbook.PlayBook
var err error
// Parse from file or text
if file != "" {
content, err := ioutil.ReadFile(file)
if err != nil {
log.Fatalf("Failed to read file: %v", err)
}
pb, err = playbook.NewFromText(string(content))
} else if text != "" {
pb, err = playbook.NewFromText(text)
} else {
log.Fatalf("Either -file or -text must be provided")
}
if err != nil {
log.Fatalf("Failed to parse heroscript: %v", err)
}
// Print the parsed playbook
fmt.Printf("Parsed %d actions:\n\n", len(pb.Actions))
for i, action := range pb.Actions {
fmt.Printf("Action %d: %s.%s (Priority: %d)\n", i+1, action.Actor, action.Name, action.Priority)
if action.Comments != "" {
fmt.Printf(" Comments: %s\n", action.Comments)
}
fmt.Printf(" Parameters:\n")
for key, value := range action.Params.GetAll() {
// Format multiline values
if strings.Contains(value, "\n") {
fmt.Printf(" %s: |\n", key)
lines := strings.Split(value, "\n")
for _, line := range lines {
fmt.Printf(" %s\n", line)
}
} else {
fmt.Printf(" %s: %s\n", key, value)
}
}
fmt.Println()
}
// Print the generated heroscript
fmt.Println("Generated HeroScript:")
fmt.Println("---------------------")
fmt.Println(pb.HeroScript(true))
fmt.Println("---------------------")
}
func handleExecuteCommand(file, text string, priority int, actor, action string) {
var pb *playbook.PlayBook
var err error
// Parse from file or text
if file != "" {
content, err := ioutil.ReadFile(file)
if err != nil {
log.Fatalf("Failed to read file: %v", err)
}
pb, err = playbook.NewFromText(string(content))
} else if text != "" {
pb, err = playbook.NewFromText(text)
} else {
log.Fatalf("Either -file or -text must be provided")
}
if err != nil {
log.Fatalf("Failed to parse heroscript: %v", err)
}
// Find actions to execute
var actionsToExecute []*playbook.Action
if actor != "" || action != "" {
// Find specific actions
actionsToExecute, err = pb.FindActions(0, actor, action, playbook.ActionTypeUnknown)
if err != nil {
log.Fatalf("Failed to find actions: %v", err)
}
} else {
// Execute all actions in priority order
actionsToExecute, err = pb.ActionsSorted(false)
if err != nil {
log.Fatalf("Failed to sort actions: %v", err)
}
}
// Execute the actions
fmt.Printf("Executing %d actions:\n\n", len(actionsToExecute))
for i, action := range actionsToExecute {
fmt.Printf("Executing action %d: %s.%s\n", i+1, action.Actor, action.Name)
// In a real implementation, you would have handlers for different actors and actions
// For this example, we'll just simulate execution
fmt.Printf(" Parameters:\n")
for key, value := range action.Params.GetAll() {
fmt.Printf(" %s: %s\n", key, value)
}
// Mark the action as done
action.Done = true
// Set some result data
action.Result.Set("status", "success")
action.Result.Set("execution_time", "0.5s")
fmt.Printf(" Result: success\n\n")
}
// Check if all actions are done
err = pb.EmptyCheck()
if err != nil {
fmt.Printf("Warning: %v\n", err)
} else {
fmt.Println("All actions executed successfully!")
}
}

View File

@@ -0,0 +1,2 @@
herohandler
example

View File

@@ -0,0 +1,106 @@
# HeroHandler Example
This package demonstrates how to implement and use a handler for HeroScript in the HeroLauncher project.
## Overview
The HeroHandler example provides a simple key-value store implementation that showcases how to:
1. Create a custom handler that extends the base handler functionality
2. Implement action methods that can be called via HeroScript
3. Parse parameters from HeroScript actions
4. Process and execute HeroScript commands
## Project Structure
```
./herohandler/
├── README.md # This documentation file
├── main.go # Main executable that uses the example handler
└── internal/ # Internal package for the example handler implementation
└── example_handler.go # Example handler implementation
```
## Handler Actions
The example handler supports the following actions:
- `example.set`: Store a key-value pair
- Parameters: `key`, `value`
- `example.get`: Retrieve a value by key
- Parameters: `key`
- `example.list`: List all stored key-value pairs
- No parameters
- `example.delete`: Remove a key-value pair
- Parameters: `key`
## Usage
You can run the example handler using the provided `main.go`:
```bash
# Build the binary
cd pkg/heroscript/cmd/herohandler
go build -o herohandler
# Set a key-value pair
./herohandler "example.set key:mykey value:myvalue"
# Get a value by key
./herohandler "example.get key:mykey"
# List all stored key-value pairs
./herohandler "example.list"
# Delete a key-value pair
./herohandler "example.delete key:mykey"
```
### Important Note on State Persistence
The example handler maintains its key-value store in memory only for the duration of a single command execution. Each time you run the `herohandler` command, a new instance of the handler is created with an empty data store. This is by design to keep the example simple.
In a real-world application, you would typically implement persistence using a database, file storage, or other mechanisms to maintain state between command executions.
### Multi-Command Example
To execute multiple commands in a single script, you can create a HeroScript file and pass it to the handler. For example:
```bash
# Create a script file
cat > example.hero << EOF
!!example.set key:user value:john
!!example.set key:role value:admin
!!example.list
EOF
# Run the script
cat example.hero | ./herohandler
```
This would process all commands in a single execution, allowing the in-memory state to be shared between commands.
## Implementation Details
The example handler demonstrates several important concepts:
1. **Handler Structure**: The `ExampleHandler` extends the `handlers.BaseHandler` which provides common functionality for all handlers.
2. **Action Methods**: Each action is implemented as a method on the handler struct (e.g., `Set`, `Get`, `List`, `Delete`).
3. **Parameter Parsing**: The `ParseParams` method from `BaseHandler` is used to extract parameters from HeroScript.
4. **Action Execution**: The `Play` method from `BaseHandler` uses reflection to find and call the appropriate method based on the action name.
5. **In-Memory Storage**: The example handler maintains a simple in-memory key-value store using a map.
## Extending the Example
To create your own handler:
1. Create a new struct that embeds the `handlers.BaseHandler`
2. Implement methods for each action your handler will support
3. Create a constructor function that initializes your handler with the appropriate actor name
4. Use the `Play` method to process HeroScript commands
For more complex handlers, you might need to add additional fields to store state or configuration.

View File

@@ -0,0 +1,7 @@
!!example.set key:username value:johndoe
!!example.set key:email value:john@example.com
!!example.set key:role value:admin
!!example.list
!!example.get key:username
!!example.delete key:email
!!example.list

View File

@@ -0,0 +1,102 @@
package internal
import (
"fmt"
"git.ourworld.tf/herocode/heroagent/pkg/handlerfactory"
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/handlers"
)
// ExampleHandler handles example actions
type ExampleHandler struct {
handlers.BaseHandler
data map[string]string
}
// NewExampleHandler creates a new example handler
func NewExampleHandler() *ExampleHandler {
return &ExampleHandler{
BaseHandler: handlers.BaseHandler{
BaseHandler: handlerfactory.BaseHandler{
ActorName: "example",
},
},
data: make(map[string]string),
}
}
// Set handles the example.set action
func (h *ExampleHandler) Set(script string) string {
params, err := h.BaseHandler.ParseParams(script)
if err != nil {
return fmt.Sprintf("Error parsing parameters: %v", err)
}
key := params.Get("key")
if key == "" {
return "Error: key is required"
}
value := params.Get("value")
if value == "" {
return "Error: value is required"
}
h.data[key] = value
return fmt.Sprintf("Set %s = %s", key, value)
}
// Get handles the example.get action
func (h *ExampleHandler) Get(script string) string {
params, err := h.BaseHandler.ParseParams(script)
if err != nil {
return fmt.Sprintf("Error parsing parameters: %v", err)
}
key := params.Get("key")
if key == "" {
return "Error: key is required"
}
value, exists := h.data[key]
if !exists {
return fmt.Sprintf("Key '%s' not found", key)
}
return fmt.Sprintf("%s = %s", key, value)
}
// List handles the example.list action
func (h *ExampleHandler) List(script string) string {
if len(h.data) == 0 {
return "No data stored"
}
var result string
for key, value := range h.data {
result += fmt.Sprintf("%s = %s\n", key, value)
}
return result
}
// Delete handles the example.delete action
func (h *ExampleHandler) Delete(script string) string {
params, err := h.BaseHandler.ParseParams(script)
if err != nil {
return fmt.Sprintf("Error parsing parameters: %v", err)
}
key := params.Get("key")
if key == "" {
return "Error: key is required"
}
_, exists := h.data[key]
if !exists {
return fmt.Sprintf("Key '%s' not found", key)
}
delete(h.data, key)
return fmt.Sprintf("Deleted key '%s'", key)
}

View File

@@ -0,0 +1,101 @@
package main
import (
"bufio"
"fmt"
"io"
"os"
"strings"
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/cmd/herohandler/internal"
)
func main() {
// Create a new example handler
handler := internal.NewExampleHandler()
// Check if input is coming from stdin (piped input)
stat, _ := os.Stdin.Stat()
if (stat.Mode() & os.ModeCharDevice) == 0 {
// Reading from stdin (pipe or redirect)
processStdin(handler)
return
}
// Check if there are command-line arguments
if len(os.Args) < 2 {
printUsage()
return
}
// 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, "!!") {
script = "!!" + script
}
// Process the script
result, err := handler.Play(script, handler)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
// Print the result
fmt.Println(result)
}
func printUsage() {
fmt.Println("Usage: herohandler <action>")
fmt.Println(" cat script.hero | herohandler")
fmt.Println("\nExample commands:")
fmt.Println(" example.set key:mykey value:myvalue")
fmt.Println(" example.get key:mykey")
fmt.Println(" example.list")
fmt.Println(" example.delete key:mykey")
fmt.Println("\nNote: The command will be automatically formatted as HeroScript with !! prefix.")
fmt.Println(" You can also pipe a multi-line HeroScript file to process multiple commands.")
}
// processStdin reads and processes HeroScript from stdin
func processStdin(handler *internal.ExampleHandler) {
reader := bufio.NewReader(os.Stdin)
var scriptBuilder strings.Builder
// Read all lines from stdin
for {
line, err := reader.ReadString('\n')
if err != nil && err != io.EOF {
fmt.Printf("Error reading from stdin: %v\n", err)
return
}
// Add the line to our script
scriptBuilder.WriteString(line)
// If we've reached EOF, break
if err == io.EOF {
break
}
}
// Process the complete script
script := scriptBuilder.String()
if script == "" {
fmt.Println("Error: Empty script")
return
}
// Process the script
result, err := handler.Play(script, handler)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
// Print the result
fmt.Println(result)
}

View File

@@ -0,0 +1,208 @@
package main
import (
"bufio"
"fmt"
"log"
"net"
"os"
"strings"
"syscall"
"time"
"git.ourworld.tf/herocode/heroagent/pkg/handlerfactory/herohandler"
)
func main() {
// Start the herohandler in a goroutine
go startHeroHandler()
// Run a simple telnet test
time.Sleep(1 * time.Second) // Give the server time to start
fmt.Println("\n=== Testing HeroHandler Telnet Server ===")
fmt.Println("The herohandler is running with a process manager handler registered.")
fmt.Println("You can connect to the telnet server at:")
fmt.Println("- Unix socket: /tmp/hero.sock")
fmt.Println("- TCP address: localhost:8023")
fmt.Println("\nYou can use the following command to connect:")
fmt.Println(" telnet localhost 8023")
fmt.Println(" nc -U /tmp/hero.sock")
// Start interactive shell
fmt.Println("\n=== Interactive Shell ===")
fmt.Println("Type 'help' to see available commands")
fmt.Println("Type 'exit' to quit")
startInteractiveShell()
}
func startInteractiveShell() {
reader := bufio.NewReader(os.Stdin)
for {
fmt.Print("> ")
input, err := reader.ReadString('\n')
if err != nil {
fmt.Println("Error reading input:", err)
continue
}
input = strings.TrimSpace(input)
switch input {
case "exit", "quit":
fmt.Println("Exiting...")
// Send termination signal to the herohandler goroutine
syscall.Kill(syscall.Getpid(), syscall.SIGINT)
return
case "help":
showHelp()
case "status":
fmt.Println("HeroHandler is running")
fmt.Println("Telnet server is active at:")
fmt.Println("- Unix socket: /tmp/hero.sock")
fmt.Println("- TCP address: localhost:8023")
case "actions":
showSupportedActions()
case "test":
runTestScript()
default:
if input != "" {
fmt.Println("Unknown command. Type 'help' to see available commands.")
}
}
}
}
func showHelp() {
fmt.Println("Available commands:")
fmt.Println(" help - Show this help message")
fmt.Println(" status - Show herohandler status")
fmt.Println(" actions - Show supported actions for registered handlers")
fmt.Println(" test - Run a test heroscript")
fmt.Println(" exit - Exit the program")
}
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 {
fmt.Printf("TCP connection failed, trying Unix socket: %v\n", err)
result, err = Send(script, "/tmp/hero.sock", true)
if err != nil {
fmt.Printf("Error getting supported actions: %v\n", err)
return
}
}
fmt.Println("Supported actions by actor:")
fmt.Println(result)
}
// Send connects to the telnet server and sends a command, returning the response
func Send(command string, address string, isUnixSocket bool) (string, error) {
var conn net.Conn
var err error
// Connect to the server based on the address type
if isUnixSocket {
conn, err = net.Dial("unix", address)
} else {
conn, err = net.Dial("tcp", address)
}
if err != nil {
return "", fmt.Errorf("error connecting to server: %v", err)
}
defer conn.Close()
// Create a reader for the connection
reader := bufio.NewReader(conn)
// Read the welcome message
_, err = reader.ReadString('\n')
if err != nil {
return "", fmt.Errorf("error reading welcome message: %v", err)
}
// Send the command
fmt.Fprintf(conn, "%s\n", command)
// Read the response with a timeout
result := ""
ch := make(chan string)
errCh := make(chan error)
go func() {
var response strings.Builder
for {
line, err := reader.ReadString('\n')
if err != nil {
errCh <- fmt.Errorf("error reading response: %v", err)
return
}
response.WriteString(line)
// If we've received a complete response, break
if strings.Contains(line, "\n") && strings.TrimSpace(line) == "" {
break
}
}
ch <- response.String()
}()
select {
case result = <-ch:
return result, nil
case err = <-errCh:
return "", err
case <-time.After(5 * time.Second):
return "", fmt.Errorf("timeout waiting for response")
}
}
func runTestScript() {
// Simple test script for the process manager
script := `
!!process.list format:json
`
fmt.Println("Running test script:")
fmt.Println(script)
// Send the script to the telnet server
// Try TCP first, then Unix socket if TCP fails
result, err := Send(script, "localhost:8023", false)
if err != nil {
fmt.Printf("TCP connection failed, trying Unix socket: %v\n", err)
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
}
}
fmt.Println("Result:")
fmt.Println(result)
}
func startHeroHandler() {
if err := herohandler.Init(); err != nil {
log.Fatalf("Failed to start telnet server: %v", err)
}
if err := herohandler.StartTelnet(); err != nil {
log.Fatalf("Failed to start telnet server: %v", err)
}
}

View File

@@ -0,0 +1 @@
heroscriptexample

View File

@@ -0,0 +1,75 @@
package main
import (
"fmt"
"log"
"git.ourworld.tf/herocode/heroagent/pkg/heroscript/playbook"
)
const exampleScript = `
//This is a mail client configuration
!!mailclient.configure
name: 'mymail'
host: 'smtp.example.com'
port: 25
secure: 1
reset: 1
description: '
This is a multiline description
for my mail client configuration.
It supports multiple paragraphs.
'
//System update action
!!system.update
force: 1
packages: 'git,curl,wget'
`
func main() {
// Parse heroscript
pb, err := playbook.NewFromText(exampleScript)
if err != nil {
log.Fatalf("Failed to parse heroscript: %v", err)
}
// Print the playbook
fmt.Println("Playbook contains:")
fmt.Printf("- %d actions\n", len(pb.Actions))
fmt.Println("- Hash: " + pb.HashKey())
fmt.Println()
// Print each action
for i, action := range pb.Actions {
fmt.Printf("Action %d: %s.%s\n", i+1, action.Actor, action.Name)
fmt.Printf(" Comments: %s\n", action.Comments)
fmt.Printf(" Parameters:\n")
for key, value := range action.Params.GetAll() {
fmt.Printf(" %s: %s\n", key, value)
}
fmt.Println()
}
// Generate heroscript
fmt.Println("Generated HeroScript:")
fmt.Println("---------------------")
fmt.Println(pb.HeroScript(true))
fmt.Println("---------------------")
// Demonstrate finding actions
mailActions, err := pb.FindActions(0, "mailclient", "", playbook.ActionTypeUnknown)
if err != nil {
log.Fatalf("Error finding actions: %v", err)
}
fmt.Printf("\nFound %d mail client actions\n", len(mailActions))
// Mark an action as done
if len(pb.Actions) > 0 {
pb.Actions[0].Done = true
fmt.Println("\nAfter marking first action as done:")
fmt.Println(pb.HeroScript(false)) // Don't show done actions
}
}

View File

@@ -0,0 +1,19 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
vmhandler
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work

View File

@@ -0,0 +1,134 @@
# VM Handler Example
This example demonstrates how to use the HandlerFactory with a VM handler to process heroscript commands.
## Overview
The VM handler example shows how to:
1. Create a handler that processes VM-related actions
2. Register the handler with the HandlerFactory
3. Start a telnet server that uses the HandlerFactory to process commands
4. Connect to the telnet server and send heroscript commands
## Running the Example
To run the example:
```bash
cd ~/code/github/freeflowuniverse/herocode/heroagent/pkg/handlerfactory/cmd/vmhandler
go run . tutorial
#to run just the server do
go run .
#you can then go to other terminal and play with telnet / nc
```
This will start a telnet server on:
- Unix socket: `/tmp/vmhandler.sock`
- TCP: `localhost:8024`
## Connecting to the Server
### Using Unix Socket
```bash
nc -U /tmp/vmhandler.sock
```
### Using TCP
```bash
telnet localhost 8024
```
## Authentication
When you connect, you'll need to authenticate with the secret:
```
!!auth secret:1234
```
## Available Commands
Once authenticated, you can use the following commands:
```
!!vm.define name:'test_vm' cpu:4 memory:'8GB' storage:'100GB'
!!vm.start name:'test_vm'
!!vm.stop name:'test_vm'
!!vm.disk_add name:'test_vm' size:'50GB' type:'SSD'
!!vm.list
!!vm.status name:'test_vm'
!!vm.delete name:'test_vm' force:true
```
## Example Session
Here's an example session:
```
$ telnet localhost 8024
Connected to localhost.
Escape character is '^]'.
** Welcome: you are not authenticated, provide secret.
!!auth secret:1234
** Welcome: you are authenticated.
!!vm.define name:'test_vm' cpu:4 memory:'8GB' storage:'100GB'
VM 'test_vm' defined successfully with 4 CPU, 8GB memory, and 100GB storage
!!vm.start name:'test_vm'
VM 'test_vm' started successfully
!!vm.disk_add name:'test_vm' size:'50GB' type:'SSD'
Added 50GB SSD disk to VM 'test_vm'
!!vm.status name:'test_vm'
VM 'test_vm' status:
- Status: running
- CPU: 4
- Memory: 8GB
- Storage: 100GB
- Attached disks:
1. 50GB SSD
!!vm.list
Defined VMs:
- test_vm (running): 4 CPU, 8GB memory, 100GB storage
Attached disks:
1. 50GB SSD
!!vm.stop name:'test_vm'
VM 'test_vm' stopped successfully
!!vm.delete name:'test_vm'
VM 'test_vm' deleted successfully
!!quit
Goodbye!
Connection closed by foreign host.
```
## Other Commands
- `!!help`, `h`, or `?` - Show help
- `!!interactive` or `!!i` - Toggle interactive mode (with colors)
- `!!quit`, `!!exit`, or `q` - Disconnect from server
## How It Works
1. The `main.go` file creates a HandlerFactory and registers the VM handler
2. It starts a telnet server that uses the HandlerFactory to process commands
3. When a client connects and sends a heroscript command, the server:
- Parses the command to determine the actor and action
- Calls the appropriate method on the VM handler
- Returns the result to the client
## Extending the Example
You can extend this example by:
1. Adding more methods to the VM handler
2. Creating new handlers for different actors
3. Registering multiple handlers with the HandlerFactory

View File

@@ -0,0 +1,276 @@
package main
import (
"bufio"
"fmt"
"os"
"time"
"git.ourworld.tf/herocode/heroagent/pkg/handlerfactory"
)
// runTutorial runs an interactive tutorial demonstrating the VM handler
func runTutorial() {
fmt.Println("=== VM Handler Tutorial ===")
fmt.Println("This tutorial will demonstrate how to use the VM handler with heroscript commands.")
fmt.Println("Press Enter to continue through each step...")
waitForEnter()
// Create a new handler factory
fmt.Println("\nStep 1: Create a new HandlerFactory")
fmt.Println("factory := handlerfactory.NewHandlerFactory()")
factory := handlerfactory.NewHandlerFactory()
waitForEnter()
// Create a VM handler
fmt.Println("\nStep 2: Create a VM handler")
fmt.Println("vmHandler := NewVMHandler()")
vmHandler := NewVMHandler()
waitForEnter()
// Register the VM handler with the factory
fmt.Println("\nStep 3: Register the VM handler with the factory")
fmt.Println("factory.RegisterHandler(vmHandler)")
err := factory.RegisterHandler(vmHandler)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Println("Handler registered successfully!")
waitForEnter()
// Show available actions
fmt.Println("\nStep 4: List available actions for the VM handler")
actions := factory.GetSupportedActions()
fmt.Println("Supported actions for 'vm' actor:")
for _, action := range actions["vm"] {
fmt.Printf("- %s\n", action)
}
waitForEnter()
// 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'`
fmt.Println("\nCommand:")
fmt.Println(defineScript)
fmt.Println("\nProcessing...")
time.Sleep(1 * time.Second)
result, err := factory.ProcessHeroscript(defineScript)
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Result: %s\n", result)
}
waitForEnter()
// Start the VM
startScript := `!!vm.start name:'tutorial_vm'`
fmt.Println("\nCommand:")
fmt.Println(startScript)
fmt.Println("\nProcessing...")
time.Sleep(1 * time.Second)
result, err = factory.ProcessHeroscript(startScript)
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Result: %s\n", result)
}
waitForEnter()
// Add a disk
diskAddScript := `!!vm.disk_add name:'tutorial_vm' size:'20GB' type:'SSD'`
fmt.Println("\nCommand:")
fmt.Println(diskAddScript)
fmt.Println("\nProcessing...")
time.Sleep(1 * time.Second)
result, err = factory.ProcessHeroscript(diskAddScript)
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Result: %s\n", result)
}
waitForEnter()
// Check VM status
statusScript := `!!vm.status name:'tutorial_vm'`
fmt.Println("\nCommand:")
fmt.Println(statusScript)
fmt.Println("\nProcessing...")
time.Sleep(1 * time.Second)
result, err = factory.ProcessHeroscript(statusScript)
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Result: %s\n", result)
}
waitForEnter()
// List all VMs
listScript := `!!vm.list`
fmt.Println("\nCommand:")
fmt.Println(listScript)
fmt.Println("\nProcessing...")
time.Sleep(1 * time.Second)
result, err = factory.ProcessHeroscript(listScript)
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Result: %s\n", result)
}
waitForEnter()
// Stop the VM
stopScript := `!!vm.stop name:'tutorial_vm'`
fmt.Println("\nCommand:")
fmt.Println(stopScript)
fmt.Println("\nProcessing...")
time.Sleep(1 * time.Second)
result, err = factory.ProcessHeroscript(stopScript)
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Result: %s\n", result)
}
waitForEnter()
// Delete the VM
deleteScript := `!!vm.delete name:'tutorial_vm'`
fmt.Println("\nCommand:")
fmt.Println(deleteScript)
fmt.Println("\nProcessing...")
time.Sleep(1 * time.Second)
result, err = factory.ProcessHeroscript(deleteScript)
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Result: %s\n", result)
}
waitForEnter()
// Try an invalid command
invalidScript := `!!vm.invalid name:'tutorial_vm'`
fmt.Println("\nInvalid Command:")
fmt.Println(invalidScript)
fmt.Println("\nProcessing...")
time.Sleep(1 * time.Second)
result, err = factory.ProcessHeroscript(invalidScript)
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Result: %s\n", result)
}
waitForEnter()
// Conclusion
fmt.Println("\nTutorial Complete!")
fmt.Println("You've seen how to:")
fmt.Println("1. Create a HandlerFactory")
fmt.Println("2. Register a VM handler")
fmt.Println("3. Process various heroscript commands")
fmt.Println("\nTo run the full telnet server example, execute:")
fmt.Println("go run main.go vm_handler.go")
fmt.Println("\nPress Enter to exit the tutorial...")
waitForEnter()
}
// waitForEnter waits for the user to press Enter
func waitForEnter() {
reader := bufio.NewReader(os.Stdin)
reader.ReadString('\n')
}
// VMTutorial contains the tutorial text for the VM handler
const VMTutorial = `
VM Handler Tutorial
==================
The VM handler provides a set of commands to manage virtual machines through heroscript commands.
Available VM commands:
!!vm.define name:test_vm cpu:4 memory:8GB storage:100GB
!!vm.start name:test_vm
!!vm.stop name:test_vm
!!vm.disk_add name:test_vm size:50GB type:SSD
!!vm.list
!!vm.status name:test_vm
!!vm.delete name:test_vm force:true
Authentication secret: 1234
Command Details:
--------------
1. define - Create a new VM with specified resources
Parameters:
- name: (required) Name of the VM
- cpu: (optional) Number of CPUs, default: 1
- memory: (optional) Memory size, default: 1GB
- storage: (optional) Storage size, default: 10GB
- description: (optional) Description of the VM
2. start - Start a VM
Parameters:
- name: (required) Name of the VM to start
3. stop - Stop a running VM
Parameters:
- name: (required) Name of the VM to stop
4. disk_add - Add a disk to a VM
Parameters:
- name: (required) Name of the VM
- size: (optional) Size of the disk, default: 10GB
- type: (optional) Type of disk (SSD, HDD), default: HDD
5. list - List all VMs
6. status - Show status of a VM
Parameters:
- name: (required) Name of the VM
7. delete - Delete a VM
Parameters:
- name: (required) Name of the VM
- force: (optional) Force deletion even if VM is running, default: false
8. help - Show this help message
Examples:
--------
1. Create a new VM:
!!vm.define name:webserver cpu:2 memory:4GB storage:50GB description:'Web server VM'
2. Start the VM:
!!vm.start name:webserver
3. Add an SSD disk:
!!vm.disk_add name:webserver size:100GB type:SSD
4. Check VM status:
!!vm.status name:webserver
5. List all VMs:
!!vm.list
6. Stop the VM:
!!vm.stop name:webserver
7. Delete the VM:
!!vm.delete name:webserver force:true
`
// addTutorialCommand adds the tutorial command to the main function
func addTutorialCommand() {
// Check command line arguments
if len(os.Args) > 1 && os.Args[1] == "tutorial" {
runTutorial()
os.Exit(0)
}
}
// GetVMTutorial returns the VM handler tutorial text
func GetVMTutorial() string {
return VMTutorial
}

View File

@@ -0,0 +1,285 @@
package main
import (
"fmt"
"strings"
"git.ourworld.tf/herocode/heroagent/pkg/handlerfactory"
)
// VMHandler handles VM-related actions
type VMHandler struct {
handlerfactory.BaseHandler
vms map[string]*VM
}
// VM represents a virtual machine
type VM struct {
Name string
CPU int
Memory string
Storage string
Description string
Running bool
Disks []Disk
}
// Disk represents a disk attached to a VM
type Disk struct {
Size string
Type string
}
// NewVMHandler creates a new VM handler
func NewVMHandler() *VMHandler {
return &VMHandler{
BaseHandler: handlerfactory.BaseHandler{
ActorName: "vm",
},
vms: make(map[string]*VM),
}
}
// Define handles the vm.define action
func (h *VMHandler) Define(script string) string {
params, err := h.ParseParams(script)
if err != nil {
return fmt.Sprintf("Error parsing parameters: %v", err)
}
name := params.Get("name")
if name == "" {
return "Error: VM name is required"
}
// Check if VM already exists
if _, exists := h.vms[name]; exists {
return fmt.Sprintf("Error: VM '%s' already exists", name)
}
// Create new VM
cpu := params.GetIntDefault("cpu", 1)
memory := params.Get("memory")
if memory == "" {
memory = "1GB"
}
storage := params.Get("storage")
if storage == "" {
storage = "10GB"
}
description := params.Get("description")
vm := &VM{
Name: name,
CPU: cpu,
Memory: memory,
Storage: storage,
Description: description,
Running: false,
Disks: []Disk{},
}
// Add VM to map
h.vms[name] = vm
return fmt.Sprintf("VM '%s' defined successfully with %d CPU, %s memory, and %s storage",
name, cpu, memory, storage)
}
// Start handles the vm.start action
func (h *VMHandler) Start(script string) string {
params, err := h.ParseParams(script)
if err != nil {
return fmt.Sprintf("Error parsing parameters: %v", err)
}
name := params.Get("name")
if name == "" {
return "Error: VM name is required"
}
// Find VM
vm, exists := h.vms[name]
if !exists {
return fmt.Sprintf("Error: VM '%s' not found", name)
}
// Start VM
if vm.Running {
return fmt.Sprintf("VM '%s' is already running", name)
}
vm.Running = true
return fmt.Sprintf("VM '%s' started successfully", name)
}
// Stop handles the vm.stop action
func (h *VMHandler) Stop(script string) string {
params, err := h.ParseParams(script)
if err != nil {
return fmt.Sprintf("Error parsing parameters: %v", err)
}
name := params.Get("name")
if name == "" {
return "Error: VM name is required"
}
// Find VM
vm, exists := h.vms[name]
if !exists {
return fmt.Sprintf("Error: VM '%s' not found", name)
}
// Stop VM
if !vm.Running {
return fmt.Sprintf("VM '%s' is already stopped", name)
}
vm.Running = false
return fmt.Sprintf("VM '%s' stopped successfully", name)
}
// DiskAdd handles the vm.disk_add action
func (h *VMHandler) DiskAdd(script string) string {
params, err := h.ParseParams(script)
if err != nil {
return fmt.Sprintf("Error parsing parameters: %v", err)
}
name := params.Get("name")
if name == "" {
return "Error: VM name is required"
}
// Find VM
vm, exists := h.vms[name]
if !exists {
return fmt.Sprintf("Error: VM '%s' not found", name)
}
// Add disk
size := params.Get("size")
if size == "" {
size = "10GB"
}
diskType := params.Get("type")
if diskType == "" {
diskType = "HDD"
}
disk := Disk{
Size: size,
Type: diskType,
}
vm.Disks = append(vm.Disks, disk)
return fmt.Sprintf("Added %s %s disk to VM '%s'", size, diskType, name)
}
// Delete handles the vm.delete action
func (h *VMHandler) Delete(script string) string {
params, err := h.ParseParams(script)
if err != nil {
return fmt.Sprintf("Error parsing parameters: %v", err)
}
name := params.Get("name")
if name == "" {
return "Error: VM name is required"
}
// Find VM
vm, exists := h.vms[name]
if !exists {
return fmt.Sprintf("Error: VM '%s' not found", name)
}
// Check if VM is running and force flag is not set
if vm.Running && !params.GetBool("force") {
return fmt.Sprintf("Error: VM '%s' is running. Use force:true to delete anyway", name)
}
// Delete VM
delete(h.vms, name)
return fmt.Sprintf("VM '%s' deleted successfully", name)
}
// List handles the vm.list action
func (h *VMHandler) List(script string) string {
if len(h.vms) == 0 {
return "No VMs defined"
}
var result strings.Builder
result.WriteString("Defined VMs:\n")
for _, vm := range h.vms {
status := "stopped"
if vm.Running {
status = "running"
}
result.WriteString(fmt.Sprintf("- %s (%s): %d CPU, %s memory, %s storage\n",
vm.Name, status, vm.CPU, vm.Memory, vm.Storage))
if len(vm.Disks) > 0 {
result.WriteString(" Attached disks:\n")
for i, disk := range vm.Disks {
result.WriteString(fmt.Sprintf(" %d. %s %s\n", i+1, disk.Size, disk.Type))
}
}
}
return result.String()
}
// Help handles the vm.help action
func (h *VMHandler) Help(script string) string {
return GetVMTutorial()
}
// Status handles the vm.status action
func (h *VMHandler) Status(script string) string {
params, err := h.ParseParams(script)
if err != nil {
return fmt.Sprintf("Error parsing parameters: %v", err)
}
name := params.Get("name")
if name == "" {
return "Error: VM name is required"
}
// Find VM
vm, exists := h.vms[name]
if !exists {
return fmt.Sprintf("Error: VM '%s' not found", name)
}
// Return VM status
status := "stopped"
if vm.Running {
status = "running"
}
var result strings.Builder
result.WriteString(fmt.Sprintf("VM '%s' status:\n", name))
result.WriteString(fmt.Sprintf("- Status: %s\n", status))
result.WriteString(fmt.Sprintf("- CPU: %d\n", vm.CPU))
result.WriteString(fmt.Sprintf("- Memory: %s\n", vm.Memory))
result.WriteString(fmt.Sprintf("- Storage: %s\n", vm.Storage))
if vm.Description != "" {
result.WriteString(fmt.Sprintf("- Description: %s\n", vm.Description))
}
if len(vm.Disks) > 0 {
result.WriteString("- Attached disks:\n")
for i, disk := range vm.Disks {
result.WriteString(fmt.Sprintf(" %d. %s %s\n", i+1, disk.Size, disk.Type))
}
}
return result.String()
}

View File

@@ -0,0 +1,75 @@
package main
import (
"fmt"
"log"
"os"
"os/signal"
"path/filepath"
"syscall"
"git.ourworld.tf/herocode/heroagent/pkg/handlerfactory"
)
// The tutorial functions are defined in tutorial.go
func main() {
// Check if tutorial mode is requested
addTutorialCommand()
fmt.Println("Starting VM Handler Example")
// Create a new handler factory
factory := handlerfactory.NewHandlerFactory()
// Create and register the VM handler
vmHandler := NewVMHandler()
err := factory.RegisterHandler(vmHandler)
if err != nil {
log.Fatalf("Failed to register VM handler: %v", err)
}
// Create a telnet server with the handler factory
server := handlerfactory.NewTelnetServer(factory, "1234")
// Create socket directory if it doesn't exist
socketDir := "/tmp"
err = os.MkdirAll(socketDir, 0755)
if err != nil {
log.Fatalf("Failed to create socket directory: %v", err)
}
// Start the telnet server on a Unix socket
socketPath := filepath.Join(socketDir, "vmhandler.sock")
err = server.Start(socketPath)
if err != nil {
log.Fatalf("Failed to start telnet server: %v", err)
}
fmt.Printf("Telnet server started on socket: %s\n", socketPath)
fmt.Printf("Connect with: nc -U %s\n", socketPath)
// Also start on TCP port for easier access
err = server.StartTCP("localhost:8024")
if err != nil {
log.Fatalf("Failed to start TCP telnet server: %v", err)
}
fmt.Println("Telnet server started on TCP: localhost:8024")
fmt.Println("Connect with: telnet localhost 8024")
// Print available commands
fmt.Println("\nVM Handler started. Type '!!vm.help' to see available commands.")
fmt.Println("Authentication secret: 1234")
// Wait for interrupt signal
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
// Stop the server
fmt.Println("Stopping server...")
err = server.Stop()
if err != nil {
log.Fatalf("Failed to stop telnet server: %v", err)
}
fmt.Println("Telnet server stopped")
}