diff --git a/deps b/deps new file mode 100755 index 0000000..01cc308 Binary files /dev/null and b/deps differ diff --git a/go.mod b/go.mod index fbb528e..0fda14b 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module dappco.re/go/core/ide go 1.26.0 require ( + dappco.re/go/core v0.8.0-alpha.1 dappco.re/go/core/scm v0.4.0 forge.lthn.ai/core/api v0.1.5 forge.lthn.ai/core/config v0.1.8 @@ -30,7 +31,7 @@ require ( require ( dappco.re/go/core/io v0.2.0 // indirect - dappco.re/go/core/log v0.1.0 + dappco.re/go/core/log v0.1.0 // indirect dario.cat/mergo v1.0.2 // indirect forge.lthn.ai/core/go-ai v0.1.11 // indirect forge.lthn.ai/core/go-io v0.1.7 // indirect diff --git a/go.sum b/go.sum index 29ea80f..9394705 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +dappco.re/go/core v0.8.0-alpha.1 h1:gj7+Scv+L63Z7wMxbJYHhaRFkHJo2u4MMPuUSv/Dhtk= +dappco.re/go/core v0.8.0-alpha.1/go.mod h1:f2/tBZ3+3IqDrg2F5F598llv0nmb/4gJVCFzM5geE4A= dappco.re/go/core/io v0.2.0 h1:zuudgIiTsQQ5ipVt97saWdGLROovbEB/zdVyy9/l+I4= dappco.re/go/core/io v0.2.0/go.mod h1:1QnQV6X9LNgFKfm8SkOtR9LLaj3bDcsOIeJOOyjbL5E= dappco.re/go/core/log v0.1.0 h1:pa71Vq2TD2aoEUQWFKwNcaJ3GBY8HbaNGqtE688Unyc= diff --git a/main.go b/main.go index 55470d6..ebd4177 100644 --- a/main.go +++ b/main.go @@ -4,13 +4,13 @@ import ( "context" "embed" "io/fs" - "log" "net/http" "os" "os/signal" "runtime" "syscall" + corelib "dappco.re/go/core" "dappco.re/go/core/ide/icons" "forge.lthn.ai/core/api" "forge.lthn.ai/core/api/pkg/provider" @@ -45,7 +45,8 @@ func main() { cwd, err := os.Getwd() if err != nil { - log.Fatalf("failed to get working directory: %v", err) + corelib.Error("failed to get working directory", "err", err) + os.Exit(1) } // ── Shared resources (built before Core) ─────────────────── @@ -103,13 +104,15 @@ func main() { }), ) if err != nil { - log.Fatalf("failed to create core: %v", err) + corelib.Error("failed to create core", "err", err) + os.Exit(1) } // Retrieve the MCP service for transport control mcpSvc, err := core.ServiceFor[*mcp.Service](c, "mcp") if err != nil { - log.Fatalf("failed to get MCP service: %v", err) + corelib.Error("failed to get MCP service", "err", err) + os.Exit(1) } // ── Mode selection ───────────────────────────────────────── @@ -120,25 +123,26 @@ func main() { defer cancel() if err := c.ServiceStartup(ctx, nil); err != nil { - log.Fatalf("core startup failed: %v", err) + corelib.Error("core startup failed", "err", err) + os.Exit(1) } bridge.Start(ctx) go hub.Run(ctx) // Start runtime providers if err := rm.StartAll(ctx); err != nil { - log.Printf("runtime provider error: %v", err) + corelib.Warn("runtime provider error", "err", err) } // Start API server in background for provider endpoints go func() { if err := engine.Serve(ctx); err != nil { - log.Printf("API server error: %v", err) + corelib.Warn("API server error", "err", err) } }() if err := mcpSvc.ServeStdio(ctx); err != nil { - log.Printf("MCP stdio error: %v", err) + corelib.Warn("MCP stdio error", "err", err) } rm.StopAll() @@ -154,27 +158,28 @@ func main() { defer cancel() if err := c.ServiceStartup(ctx, nil); err != nil { - log.Fatalf("core startup failed: %v", err) + corelib.Error("core startup failed", "err", err) + os.Exit(1) } bridge.Start(ctx) go hub.Run(ctx) // Start runtime providers if err := rm.StartAll(ctx); err != nil { - log.Printf("runtime provider error: %v", err) + corelib.Warn("runtime provider error", "err", err) } // Start API server go func() { - log.Printf("API server listening on %s", apiAddr) + corelib.Info("API server listening", "addr", apiAddr) if err := engine.Serve(ctx); err != nil { - log.Printf("API server error: %v", err) + corelib.Warn("API server error", "err", err) } }() go func() { if err := mcpSvc.Run(ctx); err != nil { - log.Printf("MCP error: %v", err) + corelib.Warn("MCP error", "err", err) } }() @@ -189,7 +194,8 @@ func main() { // ── GUI mode ─────────────────────────────────────────────── staticAssets, err := fs.Sub(assets, "frontend/dist/wails-angular-template/browser") if err != nil { - log.Fatal(err) + corelib.Error("failed to load static assets", "err", err) + os.Exit(1) } app := application.New(application.Options{ @@ -276,26 +282,27 @@ func main() { // Start runtime providers if err := rm.StartAll(ctx); err != nil { - log.Printf("runtime provider error: %v", err) + corelib.Warn("runtime provider error", "err", err) } // Start API server go func() { - log.Printf("API server listening on %s", apiAddr) + corelib.Info("API server listening", "addr", apiAddr) if err := engine.Serve(ctx); err != nil { - log.Printf("API server error: %v", err) + corelib.Warn("API server error", "err", err) } }() if err := mcpSvc.Run(ctx); err != nil { - log.Printf("MCP error: %v", err) + corelib.Warn("MCP error", "err", err) } }() - log.Println("Starting Core IDE...") + corelib.Info("Starting Core IDE...") if err := app.Run(); err != nil { - log.Fatal(err) + corelib.Error("application error", "err", err) + os.Exit(1) } } diff --git a/providers_test.go b/providers_test.go index 86b5e34..e037d93 100644 --- a/providers_test.go +++ b/providers_test.go @@ -1,11 +1,11 @@ package main import ( - "encoding/json" "net/http" "net/http/httptest" "testing" + corelib "dappco.re/go/core" "dappco.re/go/core/scm/manifest" "forge.lthn.ai/core/api/pkg/provider" "github.com/gin-gonic/gin" @@ -41,8 +41,8 @@ func TestProvidersAPI_List_Good_Empty(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) var resp providersResponse - err := json.Unmarshal(w.Body.Bytes(), &resp) - require.NoError(t, err) + r := corelib.JSONUnmarshal(w.Body.Bytes(), &resp) + require.True(t, r.OK) assert.Empty(t, resp.Providers) } @@ -77,8 +77,8 @@ func TestProvidersAPI_List_Good_WithRuntimeProviders(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) var resp providersResponse - err := json.Unmarshal(w.Body.Bytes(), &resp) - require.NoError(t, err) + r := corelib.JSONUnmarshal(w.Body.Bytes(), &resp) + require.True(t, r.OK) require.Len(t, resp.Providers, 1) assert.Equal(t, "test-provider", resp.Providers[0].Name) assert.Equal(t, "test", resp.Providers[0].BasePath) diff --git a/runtime.go b/runtime.go index f01db98..98f8572 100644 --- a/runtime.go +++ b/runtime.go @@ -2,18 +2,15 @@ package main import ( "context" - "fmt" - "log" "net" "net/http" "os" "os/exec" - "path/filepath" "strconv" "sync" "time" - coreerr "dappco.re/go/core/log" + corelib "dappco.re/go/core" "dappco.re/go/core/scm/manifest" "dappco.re/go/core/scm/marketplace" "forge.lthn.ai/core/api" @@ -51,7 +48,7 @@ func defaultProvidersDir() string { if err != nil { home = os.TempDir() } - return filepath.Join(home, ".core", "providers") + return corelib.Path(home, ".core", "providers") } // StartAll discovers providers in ~/.core/providers/ and starts each one. @@ -64,24 +61,24 @@ func (rm *RuntimeManager) StartAll(ctx context.Context) error { dir := defaultProvidersDir() discovered, err := marketplace.DiscoverProviders(dir) if err != nil { - return coreerr.E("runtime.StartAll", "discover providers", err) + return corelib.E("runtime.StartAll", "discover providers", err) } if len(discovered) == 0 { - log.Println("runtime: no providers found in", dir) + corelib.Info("runtime: no providers found", "dir", dir) return nil } - log.Printf("runtime: discovered %d provider(s) in %s", len(discovered), dir) + corelib.Info("runtime: discovered providers", "count", len(discovered), "dir", dir) for _, dp := range discovered { rp, err := rm.startProvider(ctx, dp) if err != nil { - log.Printf("runtime: failed to start %s: %v", dp.Manifest.Code, err) + corelib.Warn("runtime: failed to start provider", "code", dp.Manifest.Code, "err", err) continue } rm.providers = append(rm.providers, rp) - log.Printf("runtime: started %s on port %d", dp.Manifest.Code, rp.Port) + corelib.Info("runtime: started provider", "code", dp.Manifest.Code, "port", rp.Port) } return nil @@ -94,13 +91,13 @@ func (rm *RuntimeManager) startProvider(ctx context.Context, dp marketplace.Disc // Assign a free port. port, err := findFreePort() if err != nil { - return nil, coreerr.E("runtime.startProvider", "find free port", err) + return nil, corelib.E("runtime.startProvider", "find free port", err) } // Resolve binary path. binaryPath := m.Binary - if !filepath.IsAbs(binaryPath) { - binaryPath = filepath.Join(dp.Dir, binaryPath) + if !corelib.PathIsAbs(binaryPath) { + binaryPath = corelib.Path(dp.Dir, binaryPath) } // Build command args. @@ -115,22 +112,22 @@ func (rm *RuntimeManager) startProvider(ctx context.Context, dp marketplace.Disc cmd.Stderr = os.Stderr if err := cmd.Start(); err != nil { - return nil, coreerr.E("runtime.startProvider", fmt.Sprintf("start binary %s", binaryPath), err) + return nil, corelib.E("runtime.startProvider", corelib.Sprintf("start binary %s", binaryPath), err) } // Wait for health check. - healthURL := fmt.Sprintf("http://127.0.0.1:%d/health", port) + healthURL := corelib.Sprintf("http://127.0.0.1:%d/health", port) if err := waitForHealth(healthURL, 10*time.Second); err != nil { // Kill the process if health check fails. _ = cmd.Process.Kill() - return nil, coreerr.E("runtime.startProvider", fmt.Sprintf("health check failed for %s", m.Code), err) + return nil, corelib.E("runtime.startProvider", corelib.Sprintf("health check failed for %s", m.Code), err) } // Register proxy provider. cfg := provider.ProxyConfig{ Name: m.Code, BasePath: m.Namespace, - Upstream: fmt.Sprintf("http://127.0.0.1:%d", port), + Upstream: corelib.Sprintf("http://127.0.0.1:%d", port), } if m.Element != nil { cfg.Element = provider.ElementSpec{ @@ -139,7 +136,7 @@ func (rm *RuntimeManager) startProvider(ctx context.Context, dp marketplace.Disc } } if m.Spec != "" { - cfg.SpecFile = filepath.Join(dp.Dir, m.Spec) + cfg.SpecFile = corelib.Path(dp.Dir, m.Spec) } proxy := provider.NewProxy(cfg) @@ -147,7 +144,7 @@ func (rm *RuntimeManager) startProvider(ctx context.Context, dp marketplace.Disc // Serve JS assets if the provider has an element source. if m.Element != nil && m.Element.Source != "" { - assetsDir := filepath.Join(dp.Dir, "assets") + assetsDir := corelib.Path(dp.Dir, "assets") if _, err := os.Stat(assetsDir); err == nil { // Assets are served at /assets/{code}/ rm.engine.Register(&staticAssetGroup{ @@ -175,7 +172,7 @@ func (rm *RuntimeManager) StopAll() { for _, rp := range rm.providers { if rp.Cmd != nil && rp.Cmd.Process != nil { - log.Printf("runtime: stopping %s (pid %d)", rp.Manifest.Code, rp.Cmd.Process.Pid) + corelib.Info("runtime: stopping provider", "code", rp.Manifest.Code, "pid", rp.Cmd.Process.Pid) _ = rp.Cmd.Process.Signal(os.Interrupt) // Give the process 5 seconds to exit gracefully. @@ -232,7 +229,7 @@ func findFreePort() (int, error) { defer l.Close() tcpAddr, ok := l.Addr().(*net.TCPAddr) if !ok { - return 0, coreerr.E("runtime.findFreePort", "unexpected address type", nil) + return 0, corelib.E("runtime.findFreePort", "unexpected address type", nil) } return tcpAddr.Port, nil } @@ -253,7 +250,7 @@ func waitForHealth(url string, timeout time.Duration) error { time.Sleep(100 * time.Millisecond) } - return coreerr.E("runtime.waitForHealth", fmt.Sprintf("timed out after %s: %s", timeout, url), nil) + return corelib.E("runtime.waitForHealth", corelib.Sprintf("timed out after %s: %s", timeout, url), nil) } // staticAssetGroup is a simple RouteGroup that serves static files.