L0b (factory bootstrap): - bootstrap.ps1 (Windows): pre-checks, interactive config, clone .factory, home.toml - bootstrap.sh (Linux/Mac): same functionality for bash - Idempotent re-run with home.toml verification L1 (Go CLI): - factory.yaml: manifest schema with components, deps, health-checks - cmd/factory: Cobra CLI (install/update/status/diagnose) - pkg/config: home.toml reader - pkg/manifest: factory.yaml parser + topological sort - pkg/health: health-check logic (file/dir/mcp_tool/command) Scripts: - build.sh: cross-compile for linux/win/darwin × amd64/arm64 - build.ps1: PowerShell cross-compile Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
132 lines
2.9 KiB
Go
132 lines
2.9 KiB
Go
package health
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"github.com/OpeItcLoc03/factory/pkg/manifest"
|
|
)
|
|
|
|
type Result struct {
|
|
Name string `json:"name"`
|
|
Healthy bool `json:"healthy"`
|
|
Version string `json:"version,omitempty"`
|
|
Message string `json:"message,omitempty"`
|
|
}
|
|
|
|
func Check(comp *manifest.Component, vars map[string]string) (*Result, error) {
|
|
if comp.HealthCheck == nil {
|
|
return &Result{
|
|
Name: comp.Name,
|
|
Healthy: true,
|
|
Message: "no health check defined",
|
|
}, nil
|
|
}
|
|
|
|
// Substitute variables in path
|
|
path := comp.HealthCheck.Path
|
|
for k, v := range vars {
|
|
path = filepath.Clean(strings.ReplaceAll(path, "{"+k+"}", v))
|
|
}
|
|
|
|
switch comp.HealthCheck.Type {
|
|
case "file_exists":
|
|
return checkFileExists(comp.Name, path)
|
|
case "directory_exists":
|
|
return checkDirExists(comp.Name, path)
|
|
case "mcp_tool":
|
|
return checkMCPTool(comp.Name, comp.HealthCheck.Tool)
|
|
case "command":
|
|
return checkCommand(comp.Name, comp.HealthCheck.Command)
|
|
default:
|
|
return &Result{
|
|
Name: comp.Name,
|
|
Healthy: false,
|
|
Message: fmt.Sprintf("unknown health check type: %s", comp.HealthCheck.Type),
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
func checkFileExists(name, path string) (*Result, error) {
|
|
if _, err := os.Stat(path); err == nil {
|
|
return &Result{Name: name, Healthy: true}, nil
|
|
}
|
|
return &Result{
|
|
Name: name,
|
|
Healthy: false,
|
|
Message: fmt.Sprintf("file not found: %s", path),
|
|
}, nil
|
|
}
|
|
|
|
func checkDirExists(name, path string) (*Result, error) {
|
|
if info, err := os.Stat(path); err == nil && info.IsDir() {
|
|
return &Result{Name: name, Healthy: true}, nil
|
|
}
|
|
return &Result{
|
|
Name: name,
|
|
Healthy: false,
|
|
Message: fmt.Sprintf("directory not found: %s", path),
|
|
}, nil
|
|
}
|
|
|
|
func checkMCPTool(name, tool string) (*Result, error) {
|
|
// Check if tool is available in ~/.claude.json
|
|
home, _ := os.UserHomeDir()
|
|
configPath := filepath.Join(home, ".claude.json")
|
|
|
|
data, err := os.ReadFile(configPath)
|
|
if err != nil {
|
|
return &Result{
|
|
Name: name,
|
|
Healthy: false,
|
|
Message: "claude.json not found",
|
|
}, nil
|
|
}
|
|
|
|
// Simple string check (proper parsing would be better)
|
|
toolKey := `"` + tool + `"`
|
|
if strings.Contains(string(data), toolKey) {
|
|
return &Result{Name: name, Healthy: true}, nil
|
|
}
|
|
|
|
return &Result{
|
|
Name: name,
|
|
Healthy: false,
|
|
Message: fmt.Sprintf("MCP tool not found: %s", tool),
|
|
}, nil
|
|
}
|
|
|
|
func checkCommand(name, cmd string) (*Result, error) {
|
|
var shell string
|
|
var args []string
|
|
|
|
switch runtime.GOOS {
|
|
case "windows":
|
|
shell = "pwsh"
|
|
args = []string{"-Command", cmd}
|
|
default:
|
|
shell = "bash"
|
|
args = []string{"-c", cmd}
|
|
}
|
|
|
|
execCmd := exec.Command(shell, args...)
|
|
output, err := execCmd.CombinedOutput()
|
|
if err != nil {
|
|
return &Result{
|
|
Name: name,
|
|
Healthy: false,
|
|
Message: fmt.Sprintf("command failed: %s", output),
|
|
}, nil
|
|
}
|
|
|
|
return &Result{
|
|
Name: name,
|
|
Healthy: true,
|
|
Message: strings.TrimSpace(string(output)),
|
|
}, nil
|
|
}
|