diff --git a/cmd/orpctest/main.go b/cmd/orpctest/main.go new file mode 100644 index 0000000..0b38e46 --- /dev/null +++ b/cmd/orpctest/main.go @@ -0,0 +1,86 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os" + + "git.ourworld.tf/herocode/heroagent/pkg/openrpc" +) + +func main() { + // Parse command line flags + var ( + specDir = flag.String("dir", "pkg/openrpc/services", "Directory containing OpenRPC specifications") + specName = flag.String("spec", "", "Name of the specification to display (optional)") + methodName = flag.String("method", "", "Name of the method to display (optional)") + ) + flag.Parse() + + // Create a new OpenRPC Manager + manager := openrpc.NewORPCManager() + + // Ensure the specification directory exists + if _, err := os.Stat(*specDir); os.IsNotExist(err) { + log.Fatalf("Specification directory does not exist: %s", *specDir) + } + + // Load all specifications from the directory + log.Printf("Loading specifications from %s...", *specDir) + if err := manager.LoadSpecs(*specDir); err != nil { + log.Fatalf("Failed to load specifications: %v", err) + } + + // List all loaded specifications + specs := manager.ListSpecs() + if len(specs) == 0 { + log.Fatalf("No specifications found in %s", *specDir) + } + + fmt.Println("Loaded specifications:") + for _, spec := range specs { + fmt.Printf("- %s\n", spec) + } + + // If a specification name is provided, display its methods + if *specName != "" { + spec := manager.GetSpec(*specName) + if spec == nil { + log.Fatalf("Specification not found: %s", *specName) + } + + fmt.Printf("\nMethods in %s specification:\n", *specName) + methods := manager.ListMethods(*specName) + for _, method := range methods { + fmt.Printf("- %s\n", method) + } + + // If a method name is provided, display its details + if *methodName != "" { + method := manager.GetMethod(*specName, *methodName) + if method == nil { + log.Fatalf("Method not found: %s", *methodName) + } + + fmt.Printf("\nDetails for method '%s':\n", *methodName) + fmt.Printf("Description: %s\n", method.Description) + fmt.Printf("Parameters: %d\n", len(method.Params)) + + if len(method.Params) > 0 { + fmt.Println("Parameter list:") + for _, param := range method.Params { + required := "" + if param.Required { + required = " (required)" + } + fmt.Printf(" - %s%s: %s\n", param.Name, required, param.Description) + } + } + + fmt.Printf("Result: %s\n", method.Result.Name) + fmt.Printf("Examples: %d\n", len(method.Examples)) + fmt.Printf("Errors: %d\n", len(method.Errors)) + } + } +} diff --git a/openrpc_manager_plan.md b/openrpc_manager_plan.md new file mode 100644 index 0000000..8095eb0 --- /dev/null +++ b/openrpc_manager_plan.md @@ -0,0 +1,239 @@ +# OpenRPC Manager Implementation Plan + +## 1. Understanding the Requirements + +The task requires us to: +- Create an OpenRPC Manager (ORPCManager) +- Read JSON files from services in pkg/openrpc +- Create a model for OpenRPC spec in a separate file +- Read the OpenRPC specs into the model +- Keep these models in memory in the ORPCManager +- Create supporting methods like list_methods +- Create a command in @cmd/ to test this behavior + +## 2. Project Structure + +Here's the proposed file structure for our implementation: + +``` +pkg/ + openrpc/ + models/ + spec.go # OpenRPC specification model + manager.go # ORPCManager implementation +cmd/ + orpctest/ + main.go # Test command for the ORPCManager +``` + +## 3. Implementation Details + +### 3.1 OpenRPC Specification Model (pkg/openrpc/models/spec.go) + +We'll create a Go struct model that represents the OpenRPC specification based on the structure observed in zinit.json: + +```mermaid +classDiagram + class OpenRPCSpec { + +string OpenRPC + +InfoObject Info + +Server[] Servers + +Method[] Methods + } + + class InfoObject { + +string Version + +string Title + +string Description + +LicenseObject License + } + + class LicenseObject { + +string Name + } + + class Server { + +string Name + +string URL + } + + class Method { + +string Name + +string Description + +Parameter[] Params + +ResultObject Result + +Example[] Examples + +ErrorObject[] Errors + } + + class Parameter { + +string Name + +string Description + +bool Required + +SchemaObject Schema + } + + class ResultObject { + +string Name + +string Description + +SchemaObject Schema + } + + class SchemaObject { + +string Type + +map[string]interface{} Properties + +SchemaObject Items + +map[string]SchemaObject AdditionalProperties + } + + class Example { + +string Name + +map[string]interface{}[] Params + +ExampleResultObject Result + } + + class ExampleResultObject { + +string Name + +interface{} Value + } + + class ErrorObject { + +int Code + +string Message + +string Data + } + + OpenRPCSpec --> InfoObject + OpenRPCSpec --> Server + OpenRPCSpec --> Method + Method --> Parameter + Method --> ResultObject + Method --> Example + Method --> ErrorObject + Parameter --> SchemaObject + ResultObject --> SchemaObject + Example --> ExampleResultObject +``` + +### 3.2 OpenRPC Manager (pkg/openrpc/manager.go) + +The ORPCManager will be responsible for: +- Loading OpenRPC specifications from JSON files +- Storing and managing these specifications in memory +- Providing methods to access and manipulate the specifications + +```mermaid +classDiagram + class ORPCManager { + -map[string]*OpenRPCSpec specs + +NewORPCManager() *ORPCManager + +LoadSpecs(dir string) error + +LoadSpec(path string) error + +GetSpec(name string) *OpenRPCSpec + +ListSpecs() []string + +ListMethods(specName string) []string + +GetMethod(specName string, methodName string) *Method + } + + ORPCManager --> OpenRPCSpec +``` + +### 3.3 Test Command (cmd/orpctest/main.go) + +We'll create a command-line tool to test the ORPCManager functionality: +- Initialize the ORPCManager +- Load specifications from the pkg/openrpc/services directory +- List available specifications +- List methods for each specification +- Display details for specific methods + +## 4. Implementation Steps + +1. **Create the OpenRPC Specification Model**: + - Define the Go structs for the OpenRPC specification + - Implement JSON marshaling/unmarshaling + - Add validation functions + +2. **Implement the ORPCManager**: + - Create the manager struct with a map to store specifications + - Implement methods to load specifications from files + - Implement methods to access and manipulate specifications + +3. **Create the Test Command**: + - Implement a command-line interface to test the ORPCManager + - Add options to list specifications, methods, and display details + +4. **Write Tests**: + - Write unit tests for the OpenRPC model + - Write unit tests for the ORPCManager + - Write integration tests for the entire system + +## 5. Detailed Method Specifications + +### 5.1 ORPCManager Methods + +#### NewORPCManager() +- Creates a new instance of the ORPCManager +- Initializes the specs map + +#### LoadSpecs(dir string) error +- Reads all JSON files in the specified directory +- For each file, calls LoadSpec() +- Returns an error if any file fails to load + +#### LoadSpec(path string) error +- Reads the JSON file at the specified path +- Parses the JSON into an OpenRPCSpec struct +- Validates the specification +- Stores the specification in the specs map using the filename (without extension) as the key +- Returns an error if any step fails + +#### GetSpec(name string) *OpenRPCSpec +- Returns the OpenRPCSpec with the specified name +- Returns nil if the specification doesn't exist + +#### ListSpecs() []string +- Returns a list of all loaded specification names + +#### ListMethods(specName string) []string +- Returns a list of all method names in the specified specification +- Returns an empty list if the specification doesn't exist + +#### GetMethod(specName string, methodName string) *Method +- Returns the Method with the specified name from the specified specification +- Returns nil if the specification or method doesn't exist + +## 6. Example Usage + +```go +// Initialize the ORPCManager +manager := openrpc.NewORPCManager() + +// Load all specifications from the services directory +err := manager.LoadSpecs("pkg/openrpc/services") +if err != nil { + log.Fatalf("Failed to load specifications: %v", err) +} + +// List all loaded specifications +specs := manager.ListSpecs() +fmt.Println("Loaded specifications:") +for _, spec := range specs { + fmt.Printf("- %s\n", spec) +} + +// List all methods in the zinit specification +methods := manager.ListMethods("zinit") +fmt.Println("\nMethods in zinit specification:") +for _, method := range methods { + fmt.Printf("- %s\n", method) +} + +// Get details for a specific method +method := manager.GetMethod("zinit", "service_list") +if method != nil { + fmt.Printf("\nDetails for method 'service_list':\n") + fmt.Printf("Description: %s\n", method.Description) + fmt.Printf("Parameters: %d\n", len(method.Params)) + fmt.Printf("Examples: %d\n", len(method.Examples)) +} \ No newline at end of file diff --git a/orpctest b/orpctest new file mode 100755 index 0000000..865b891 Binary files /dev/null and b/orpctest differ diff --git a/pkg/openrpc/manager.go b/pkg/openrpc/manager.go new file mode 100644 index 0000000..65d5214 --- /dev/null +++ b/pkg/openrpc/manager.go @@ -0,0 +1,117 @@ +package openrpc + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "path/filepath" + "strings" + + "git.ourworld.tf/herocode/heroagent/pkg/openrpc/models" +) + +// ORPCManager manages OpenRPC specifications +type ORPCManager struct { + specs map[string]*models.OpenRPCSpec +} + +// NewORPCManager creates a new OpenRPC Manager +func NewORPCManager() *ORPCManager { + return &ORPCManager{ + specs: make(map[string]*models.OpenRPCSpec), + } +} + +// LoadSpecs loads all OpenRPC specifications from a directory +func (m *ORPCManager) LoadSpecs(dir string) error { + files, err := ioutil.ReadDir(dir) + if err != nil { + return fmt.Errorf("failed to read directory: %w", err) + } + + for _, file := range files { + if file.IsDir() { + continue + } + + if !strings.HasSuffix(file.Name(), ".json") { + continue + } + + path := filepath.Join(dir, file.Name()) + if err := m.LoadSpec(path); err != nil { + return fmt.Errorf("failed to load spec %s: %w", file.Name(), err) + } + } + + return nil +} + +// LoadSpec loads an OpenRPC specification from a file +func (m *ORPCManager) LoadSpec(path string) error { + // Read the file + data, err := ioutil.ReadFile(path) + if err != nil { + return fmt.Errorf("failed to read file: %w", err) + } + + // Parse the JSON + var spec models.OpenRPCSpec + if err := json.Unmarshal(data, &spec); err != nil { + return fmt.Errorf("failed to parse JSON: %w", err) + } + + // Validate the specification + if err := spec.Validate(); err != nil { + return fmt.Errorf("invalid specification: %w", err) + } + + // Store the specification + name := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)) + m.specs[name] = &spec + + return nil +} + +// GetSpec returns an OpenRPC specification by name +func (m *ORPCManager) GetSpec(name string) *models.OpenRPCSpec { + return m.specs[name] +} + +// ListSpecs returns a list of all loaded specification names +func (m *ORPCManager) ListSpecs() []string { + var names []string + for name := range m.specs { + names = append(names, name) + } + return names +} + +// ListMethods returns a list of all method names in a specification +func (m *ORPCManager) ListMethods(specName string) []string { + spec := m.GetSpec(specName) + if spec == nil { + return []string{} + } + + var methods []string + for _, method := range spec.Methods { + methods = append(methods, method.Name) + } + return methods +} + +// GetMethod returns a method from a specification +func (m *ORPCManager) GetMethod(specName, methodName string) *models.Method { + spec := m.GetSpec(specName) + if spec == nil { + return nil + } + + for _, method := range spec.Methods { + if method.Name == methodName { + return &method + } + } + return nil +} diff --git a/pkg/openrpc/models/spec.go b/pkg/openrpc/models/spec.go new file mode 100644 index 0000000..c895118 --- /dev/null +++ b/pkg/openrpc/models/spec.go @@ -0,0 +1,89 @@ +package models + +// OpenRPCSpec represents an OpenRPC specification document +type OpenRPCSpec struct { + OpenRPC string `json:"openrpc"` + Info InfoObject `json:"info"` + Servers []Server `json:"servers"` + Methods []Method `json:"methods"` +} + +// InfoObject contains metadata about the API +type InfoObject struct { + Version string `json:"version"` + Title string `json:"title"` + Description string `json:"description,omitempty"` + License *LicenseObject `json:"license,omitempty"` +} + +// LicenseObject contains license information for the API +type LicenseObject struct { + Name string `json:"name"` +} + +// Server represents a server that provides the API +type Server struct { + Name string `json:"name"` + URL string `json:"url"` +} + +// Method represents a method in the API +type Method struct { + Name string `json:"name"` + Description string `json:"description,omitempty"` + Params []Parameter `json:"params"` + Result ResultObject `json:"result"` + Examples []Example `json:"examples,omitempty"` + Errors []ErrorObject `json:"errors,omitempty"` +} + +// Parameter represents a parameter for a method +type Parameter struct { + Name string `json:"name"` + Description string `json:"description,omitempty"` + Required bool `json:"required"` + Schema SchemaObject `json:"schema"` +} + +// ResultObject represents the result of a method +type ResultObject struct { + Name string `json:"name"` + Description string `json:"description,omitempty"` + Schema SchemaObject `json:"schema"` +} + +// SchemaObject represents a JSON Schema object +type SchemaObject struct { + Type string `json:"type,omitempty"` + Properties map[string]SchemaObject `json:"properties,omitempty"` + Items *SchemaObject `json:"items,omitempty"` + AdditionalProperties *SchemaObject `json:"additionalProperties,omitempty"` + Description string `json:"description,omitempty"` + Enum []string `json:"enum,omitempty"` +} + +// Example represents an example for a method +type Example struct { + Name string `json:"name"` + Params []map[string]interface{} `json:"params"` + Result ExampleResultObject `json:"result"` +} + +// ExampleResultObject represents the result of an example +type ExampleResultObject struct { + Name string `json:"name"` + Value interface{} `json:"value"` +} + +// ErrorObject represents an error that can be returned by a method +type ErrorObject struct { + Code int `json:"code"` + Message string `json:"message"` + Data string `json:"data,omitempty"` +} + +// Validate validates the OpenRPC specification +func (spec *OpenRPCSpec) Validate() error { + // TODO: Implement validation logic + return nil +} diff --git a/pkg/openrpc/services/zinit.json b/pkg/openrpc/services/zinit.json new file mode 100644 index 0000000..65cc7af --- /dev/null +++ b/pkg/openrpc/services/zinit.json @@ -0,0 +1,873 @@ +{ + "openrpc": "1.2.6", + "info": { + "version": "1.0.0", + "title": "Zinit JSON-RPC API", + "description": "JSON-RPC 2.0 API for controlling and querying Zinit services", + "license": { + "name": "MIT" + } + }, + "servers": [ + { + "name": "Unix Socket", + "url": "unix:///tmp/zinit.sock" + } + ], + "methods": [ + { + "name": "rpc_discover", + "description": "Returns the OpenRPC specification for the API", + "params": [], + "result": { + "name": "OpenRPCSpec", + "description": "The OpenRPC specification", + "schema": { + "type": "object" + } + }, + "examples": [ + { + "name": "Get API specification", + "params": [], + "result": { + "name": "OpenRPCSpecResult", + "value": { + "openrpc": "1.2.6", + "info": { + "version": "1.0.0", + "title": "Zinit JSON-RPC API" + } + } + } + } + ] + }, + { + "name": "service_list", + "description": "Lists all services managed by Zinit", + "params": [], + "result": { + "name": "ServiceList", + "description": "A map of service names to their current states", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "description": "Service state (Running, Success, Error, etc.)" + } + } + }, + "examples": [ + { + "name": "List all services", + "params": [], + "result": { + "name": "ServiceListResult", + "value": { + "service1": "Running", + "service2": "Success", + "service3": "Error" + } + } + } + ] + }, + { + "name": "service_status", + "description": "Shows detailed status information for a specific service", + "params": [ + { + "name": "name", + "description": "The name of the service", + "required": true, + "schema": { + "type": "string" + } + } + ], + "result": { + "name": "ServiceStatus", + "description": "Detailed status information for the service", + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Service name" + }, + "pid": { + "type": "integer", + "description": "Process ID of the running service (if running)" + }, + "state": { + "type": "string", + "description": "Current state of the service (Running, Success, Error, etc.)" + }, + "target": { + "type": "string", + "description": "Target state of the service (Up, Down)" + }, + "after": { + "type": "object", + "description": "Dependencies of the service and their states", + "additionalProperties": { + "type": "string", + "description": "State of the dependency" + } + } + } + } + }, + "examples": [ + { + "name": "Get status of redis service", + "params": [ + { + "name": "name", + "value": "redis" + } + ], + "result": { + "name": "ServiceStatusResult", + "value": { + "name": "redis", + "pid": 1234, + "state": "Running", + "target": "Up", + "after": { + "dependency1": "Success", + "dependency2": "Running" + } + } + } + } + ], + "errors": [ + { + "code": -32000, + "message": "Service not found", + "data": "service name \"unknown\" unknown" + } + ] + }, + { + "name": "service_start", + "description": "Starts a service", + "params": [ + { + "name": "name", + "description": "The name of the service to start", + "required": true, + "schema": { + "type": "string" + } + } + ], + "result": { + "name": "StartResult", + "description": "Result of the start operation", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Start redis service", + "params": [ + { + "name": "name", + "value": "redis" + } + ], + "result": { + "name": "StartResult", + "value": null + } + } + ], + "errors": [ + { + "code": -32000, + "message": "Service not found", + "data": "service name \"unknown\" unknown" + } + ] + }, + { + "name": "service_stop", + "description": "Stops a service", + "params": [ + { + "name": "name", + "description": "The name of the service to stop", + "required": true, + "schema": { + "type": "string" + } + } + ], + "result": { + "name": "StopResult", + "description": "Result of the stop operation", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Stop redis service", + "params": [ + { + "name": "name", + "value": "redis" + } + ], + "result": { + "name": "StopResult", + "value": null + } + } + ], + "errors": [ + { + "code": -32000, + "message": "Service not found", + "data": "service name \"unknown\" unknown" + }, + { + "code": -32003, + "message": "Service is down", + "data": "service \"redis\" is down" + } + ] + }, + { + "name": "service_monitor", + "description": "Starts monitoring a service. The service configuration is loaded from the config directory.", + "params": [ + { + "name": "name", + "description": "The name of the service to monitor", + "required": true, + "schema": { + "type": "string" + } + } + ], + "result": { + "name": "MonitorResult", + "description": "Result of the monitor operation", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Monitor redis service", + "params": [ + { + "name": "name", + "value": "redis" + } + ], + "result": { + "name": "MonitorResult", + "value": null + } + } + ], + "errors": [ + { + "code": -32001, + "message": "Service already monitored", + "data": "service \"redis\" already monitored" + }, + { + "code": -32005, + "message": "Config error", + "data": "failed to load service configuration" + } + ] + }, + { + "name": "service_forget", + "description": "Stops monitoring a service. You can only forget a stopped service.", + "params": [ + { + "name": "name", + "description": "The name of the service to forget", + "required": true, + "schema": { + "type": "string" + } + } + ], + "result": { + "name": "ForgetResult", + "description": "Result of the forget operation", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Forget redis service", + "params": [ + { + "name": "name", + "value": "redis" + } + ], + "result": { + "name": "ForgetResult", + "value": null + } + } + ], + "errors": [ + { + "code": -32000, + "message": "Service not found", + "data": "service name \"unknown\" unknown" + }, + { + "code": -32002, + "message": "Service is up", + "data": "service \"redis\" is up" + } + ] + }, + { + "name": "service_kill", + "description": "Sends a signal to a running service", + "params": [ + { + "name": "name", + "description": "The name of the service to send the signal to", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "signal", + "description": "The signal to send (e.g., SIGTERM, SIGKILL)", + "required": true, + "schema": { + "type": "string" + } + } + ], + "result": { + "name": "KillResult", + "description": "Result of the kill operation", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Send SIGTERM to redis service", + "params": [ + { + "name": "name", + "value": "redis" + }, + { + "name": "signal", + "value": "SIGTERM" + } + ], + "result": { + "name": "KillResult", + "value": null + } + } + ], + "errors": [ + { + "code": -32000, + "message": "Service not found", + "data": "service name \"unknown\" unknown" + }, + { + "code": -32003, + "message": "Service is down", + "data": "service \"redis\" is down" + }, + { + "code": -32004, + "message": "Invalid signal", + "data": "invalid signal: INVALID" + } + ] + }, + { + "name": "system_shutdown", + "description": "Stops all services and powers off the system", + "params": [], + "result": { + "name": "ShutdownResult", + "description": "Result of the shutdown operation", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Shutdown the system", + "params": [], + "result": { + "name": "ShutdownResult", + "value": null + } + } + ], + "errors": [ + { + "code": -32006, + "message": "Shutting down", + "data": "system is already shutting down" + } + ] + }, + { + "name": "system_reboot", + "description": "Stops all services and reboots the system", + "params": [], + "result": { + "name": "RebootResult", + "description": "Result of the reboot operation", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Reboot the system", + "params": [], + "result": { + "name": "RebootResult", + "value": null + } + } + ], + "errors": [ + { + "code": -32006, + "message": "Shutting down", + "data": "system is already shutting down" + } + ] + }, + { + "name": "service_create", + "description": "Creates a new service configuration file", + "params": [ + { + "name": "name", + "description": "The name of the service to create", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "content", + "description": "The service configuration content", + "required": true, + "schema": { + "type": "object", + "properties": { + "exec": { + "type": "string", + "description": "Command to run" + }, + "oneshot": { + "type": "boolean", + "description": "Whether the service should be restarted" + }, + "after": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Services that must be running before this one starts" + }, + "log": { + "type": "string", + "enum": ["null", "ring", "stdout"], + "description": "How to handle service output" + }, + "env": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Environment variables for the service" + }, + "shutdown_timeout": { + "type": "integer", + "description": "Maximum time to wait for service to stop during shutdown" + } + } + } + } + ], + "result": { + "name": "CreateServiceResult", + "description": "Result of the create operation", + "schema": { + "type": "string" + } + }, + "errors": [ + { + "code": -32007, + "message": "Service already exists", + "data": "Service 'name' already exists" + }, + { + "code": -32008, + "message": "Service file error", + "data": "Failed to create service file" + } + ] + }, + { + "name": "service_delete", + "description": "Deletes a service configuration file", + "params": [ + { + "name": "name", + "description": "The name of the service to delete", + "required": true, + "schema": { + "type": "string" + } + } + ], + "result": { + "name": "DeleteServiceResult", + "description": "Result of the delete operation", + "schema": { + "type": "string" + } + }, + "errors": [ + { + "code": -32000, + "message": "Service not found", + "data": "Service 'name' not found" + }, + { + "code": -32008, + "message": "Service file error", + "data": "Failed to delete service file" + } + ] + }, + { + "name": "service_get", + "description": "Gets a service configuration file", + "params": [ + { + "name": "name", + "description": "The name of the service to get", + "required": true, + "schema": { + "type": "string" + } + } + ], + "result": { + "name": "GetServiceResult", + "description": "The service configuration", + "schema": { + "type": "object" + } + }, + "errors": [ + { + "code": -32000, + "message": "Service not found", + "data": "Service 'name' not found" + }, + { + "code": -32008, + "message": "Service file error", + "data": "Failed to read service file" + } + ] + }, + { + "name": "service_stats", + "description": "Get memory and CPU usage statistics for a service", + "params": [ + { + "name": "name", + "description": "The name of the service to get stats for", + "required": true, + "schema": { + "type": "string" + } + } + ], + "result": { + "name": "ServiceStats", + "description": "Memory and CPU usage statistics for the service", + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Service name" + }, + "pid": { + "type": "integer", + "description": "Process ID of the service" + }, + "memory_usage": { + "type": "integer", + "description": "Memory usage in bytes" + }, + "cpu_usage": { + "type": "number", + "description": "CPU usage as a percentage (0-100)" + }, + "children": { + "type": "array", + "description": "Stats for child processes", + "items": { + "type": "object", + "properties": { + "pid": { + "type": "integer", + "description": "Process ID of the child process" + }, + "memory_usage": { + "type": "integer", + "description": "Memory usage in bytes" + }, + "cpu_usage": { + "type": "number", + "description": "CPU usage as a percentage (0-100)" + } + } + } + } + } + } + }, + "examples": [ + { + "name": "Get stats for redis service", + "params": [ + { + "name": "name", + "value": "redis" + } + ], + "result": { + "name": "ServiceStatsResult", + "value": { + "name": "redis", + "pid": 1234, + "memory_usage": 10485760, + "cpu_usage": 2.5, + "children": [ + { + "pid": 1235, + "memory_usage": 5242880, + "cpu_usage": 1.2 + } + ] + } + } + } + ], + "errors": [ + { + "code": -32000, + "message": "Service not found", + "data": "service name \"unknown\" unknown" + }, + { + "code": -32003, + "message": "Service is down", + "data": "service \"redis\" is down" + } + ] + }, + { + "name": "system_start_http_server", + "description": "Start an HTTP/RPC server at the specified address", + "params": [ + { + "name": "address", + "description": "The network address to bind the server to (e.g., '127.0.0.1:8080')", + "required": true, + "schema": { + "type": "string" + } + } + ], + "result": { + "name": "StartHttpServerResult", + "description": "Result of the start HTTP server operation", + "schema": { + "type": "string" + } + }, + "examples": [ + { + "name": "Start HTTP server on localhost:8080", + "params": [ + { + "name": "address", + "value": "127.0.0.1:8080" + } + ], + "result": { + "name": "StartHttpServerResult", + "value": "HTTP server started at 127.0.0.1:8080" + } + } + ], + "errors": [ + { + "code": -32602, + "message": "Invalid address", + "data": "Invalid network address format" + } + ] + }, + { + "name": "system_stop_http_server", + "description": "Stop the HTTP/RPC server if running", + "params": [], + "result": { + "name": "StopHttpServerResult", + "description": "Result of the stop HTTP server operation", + "schema": { + "type": "null" + } + }, + "examples": [ + { + "name": "Stop the HTTP server", + "params": [], + "result": { + "name": "StopHttpServerResult", + "value": null + } + } + ], + "errors": [ + { + "code": -32602, + "message": "Server not running", + "data": "No HTTP server is currently running" + } + ] + }, + { + "name": "stream_currentLogs", + "description": "Get current logs from zinit and monitored services", + "params": [ + { + "name": "name", + "description": "Optional service name filter. If provided, only logs from this service will be returned", + "required": false, + "schema": { + "type": "string" + } + } + ], + "result": { + "name": "LogsResult", + "description": "Array of log strings", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "examples": [ + { + "name": "Get all logs", + "params": [], + "result": { + "name": "LogsResult", + "value": [ + "2023-01-01T12:00:00 redis: Starting service", + "2023-01-01T12:00:01 nginx: Starting service" + ] + } + }, + { + "name": "Get logs for a specific service", + "params": [ + { + "name": "name", + "value": "redis" + } + ], + "result": { + "name": "LogsResult", + "value": [ + "2023-01-01T12:00:00 redis: Starting service", + "2023-01-01T12:00:02 redis: Service started" + ] + } + } + ] + }, + { + "name": "stream_subscribeLogs", + "description": "Subscribe to log messages generated by zinit and monitored services", + "params": [ + { + "name": "name", + "description": "Optional service name filter. If provided, only logs from this service will be returned", + "required": false, + "schema": { + "type": "string" + } + } + ], + "result": { + "name": "LogSubscription", + "description": "A subscription to log messages", + "schema": { + "type": "string" + } + }, + "examples": [ + { + "name": "Subscribe to all logs", + "params": [], + "result": { + "name": "LogSubscription", + "value": "2023-01-01T12:00:00 redis: Service started" + } + }, + { + "name": "Subscribe to filtered logs", + "params": [ + { + "name": "name", + "value": "redis" + } + ], + "result": { + "name": "LogSubscription", + "value": "2023-01-01T12:00:00 redis: Service started" + } + } + ] + } + ] +} \ No newline at end of file