package main import ( "context" "flag" "log" "os" "os/signal" "path/filepath" "syscall" "github.com/kami/hexis/internal/api" "github.com/kami/hexis/internal/execution" "github.com/kami/hexis/internal/mcp" "github.com/kami/hexis/internal/nexusclient" "github.com/kami/hexis/internal/provider" "github.com/kami/hexis/internal/storage" ) func main() { var httpAddr string var dataDir string var mcpMode bool var workspaceURL string var workspaceAllowlist string var nexusURL string flag.StringVar(&httpAddr, "http", "", "HTTP listen address (default localhost:9741)") flag.StringVar(&dataDir, "data", "", "Data directory for SQLite database") flag.BoolVar(&mcpMode, "mcp", false, "Run in MCP stdio mode") flag.StringVar(&workspaceURL, "workspace-url", "", "Workspace MCP HTTP API URL (e.g. http://localhost:9930)") flag.StringVar(&workspaceAllowlist, "workspace-allowlist", "", "Path to workspace tool allowlist YAML") flag.StringVar(&nexusURL, "nexus", "", "Nexus base URL for hexis.resolve_target (default http://localhost:8987)") flag.Parse() if dataDir == "" { dataDir = filepath.Join(os.Getenv("HOME"), ".local", "share", "hexis") } if httpAddr == "" { httpAddr = "localhost:9741" } if workspaceAllowlist == "" { workspaceAllowlist = filepath.Join(dataDir, "workspace-allowlist.yaml") } if workspaceURL == "" { workspaceURL = os.Getenv("WORKSPACE_MCP_URL") } if nexusURL == "" { nexusURL = os.Getenv("HEXIS_NEXUS_URL") } if nexusURL == "" { nexusURL = "http://localhost:8987" } dbPath := filepath.Join(dataDir, "hexis.db") store, err := storage.Open(dbPath) if err != nil { log.Fatalf("open storage: %v", err) } defer store.Close() reg := provider.NewRegistry() reg.Register(provider.NewSystemdProvider()) // Register workspace MCP provider if configured if workspaceURL != "" { allowlist, err := provider.LoadToolAllowlist(workspaceAllowlist) if err != nil { log.Fatalf("load workspace allowlist: %v", err) } wsProvider := provider.NewWorkspaceMCPProvider(workspaceURL, allowlist) reg.Register(wsProvider) tools, err := wsProvider.DiscoverTools() if err != nil { log.Printf("warning: workspace MCP discovery failed: %v", err) } else { log.Printf("discovered %d workspace tools, %d in allowlist", len(tools), len(allowlist.Tools)) } // Register workspace capabilities for _, cap := range wsProvider.BuildCapabilities() { existing, err := store.GetCapability(cap.ID) if err == nil && existing != nil { continue } if err := store.CreateCapability(&cap); err != nil { log.Printf("warning: register capability %s: %v", cap.Name, err) } else { log.Printf("registered capability: %s -> %s", cap.Name, cap.Operation) } } } engine := execution.New(store, reg) if mcpMode { log.Printf("starting MCP stdio adapter") adapter := mcp.New(store, engine, nexusclient.New(nexusURL)) if err := adapter.ServeStdio(); err != nil { log.Fatalf("MCP error: %v", err) } return } srv := api.NewServer(store, engine) ctx, cancel := context.WithCancel(context.Background()) defer cancel() go func() { log.Printf("listening on http %s", httpAddr) if err := srv.ListenHTTP(httpAddr); err != nil { log.Printf("http error: %v", err) } }() sig := make(chan os.Signal, 1) signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) <-sig log.Println("shutting down...") srv.Shutdown(ctx) log.Println("stopped") }