initial agent-mgr: app builder platform MVP

Go API server + Preact UI + Claude Code adapter.
- App-centric model (ideas, not repos)
- AgentProvider interface for multi-agent support
- K8s pod lifecycle for sandboxed agent sessions
- Gitea integration (create repos, push branches)
- WebSocket streaming for live session output
- Woodpecker CI/CD pipelines (kaniko build + kubectl deploy)
This commit is contained in:
Steven Hooker
2026-02-18 15:56:32 +01:00
commit e5b07cc1d8
39 changed files with 3120 additions and 0 deletions

77
internal/api/router.go Normal file
View File

@@ -0,0 +1,77 @@
package api
import (
"io/fs"
"log/slog"
"net/http"
"github.com/agentsphere/agent-mgr/internal/app"
"github.com/agentsphere/agent-mgr/internal/events"
"github.com/agentsphere/agent-mgr/internal/provider"
"github.com/agentsphere/agent-mgr/internal/store"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
type Server struct {
store *store.Store
app *app.Service
registry *provider.Registry
events *events.Bus
log *slog.Logger
}
func NewServer(st *store.Store, appSvc *app.Service, reg *provider.Registry, bus *events.Bus, log *slog.Logger) *Server {
return &Server{store: st, app: appSvc, registry: reg, events: bus, log: log}
}
func (s *Server) Handler(staticFS fs.FS) http.Handler {
r := chi.NewRouter()
r.Use(middleware.RealIP)
r.Use(middleware.Recoverer)
r.Use(middleware.Compress(5))
// API routes
r.Route("/api/v1", func(r chi.Router) {
r.Use(middleware.SetHeader("Content-Type", "application/json"))
r.Post("/apps", s.createApp)
r.Get("/apps", s.listApps)
r.Get("/apps/{appID}", s.getApp)
r.Delete("/apps/{appID}", s.deleteApp)
r.Post("/apps/{appID}/sessions", s.createSession)
r.Get("/apps/{appID}/sessions", s.listSessions)
r.Get("/sessions/{sessionID}", s.getSession)
r.Post("/sessions/{sessionID}/stop", s.stopSession)
r.Post("/sessions/{sessionID}/message", s.sendMessage)
r.Get("/sessions/{sessionID}/ws", s.streamSession)
r.Get("/providers", s.listProviders)
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`{"status":"ok"}`))
})
})
// Serve SPA for all other routes
if staticFS != nil {
fileServer := http.FileServer(http.FS(staticFS))
r.Get("/*", func(w http.ResponseWriter, r *http.Request) {
// Try to serve the file; if not found, serve index.html (SPA routing)
path := r.URL.Path
f, err := staticFS.Open(path[1:]) // strip leading /
if err != nil {
// Serve index.html for SPA client-side routing
r.URL.Path = "/"
} else {
f.Close()
}
fileServer.ServeHTTP(w, r)
})
}
return r
}