diff options
Diffstat (limited to 'internal/cli')
| -rw-r--r-- | internal/cli/serve.go | 43 |
1 files changed, 39 insertions, 4 deletions
diff --git a/internal/cli/serve.go b/internal/cli/serve.go index ae42780..f687e14 100644 --- a/internal/cli/serve.go +++ b/internal/cli/serve.go @@ -11,6 +11,8 @@ import ( "time" "github.com/thepeterstone/claudomator/internal/api" + "github.com/thepeterstone/claudomator/internal/budget" + "github.com/thepeterstone/claudomator/internal/config" "github.com/thepeterstone/claudomator/internal/executor" "github.com/thepeterstone/claudomator/internal/notify" "github.com/thepeterstone/claudomator/internal/storage" @@ -34,7 +36,7 @@ func newServeCmd() *cobra.Command { }, } - cmd.Flags().StringVar(&addr, "addr", ":8484", "listen address") + cmd.Flags().StringVar(&addr, "addr", "127.0.0.1:8484", "listen address (loopback by default; set external_bind_allowed=true in config to bind externally)") cmd.Flags().StringVar(&basePath, "base-path", "/claudomator", "URL prefix the UI is served under (e.g. /claudomator-oss)") cmd.Flags().StringVar(&workspaceRoot, "workspace-root", "/workspace", "root directory for listing workspaces") cmd.Flags().StringVar(&cfg.ClaudeImage, "claude-image", cfg.ClaudeImage, "docker image for claude agents") @@ -44,6 +46,9 @@ func newServeCmd() *cobra.Command { } func serve(addr, basePath string) error { + if err := config.ValidateBindAddr(addr, cfg.ExternalBindAllowed); err != nil { + return err + } if err := cfg.EnsureDirs(); err != nil { return fmt.Errorf("creating dirs: %w", err) } @@ -81,6 +86,10 @@ func serve(addr, basePath string) error { claudeConfigDir := cfg.ClaudeConfigDir repoDir, _ := os.Getwd() + + // Shared per-task agent MCP token registry: runners mint tokens; the API + // server mounts /mcp and resolves them. + agentRegistry := executor.NewRegistry() runners := map[string]executor.Runner{} // ContainerRunner: binaries are resolved via PATH inside the container image, @@ -96,6 +105,7 @@ func serve(addr, basePath string) error { ClaudeConfigDir: claudeConfigDir, CredentialSyncCmd: filepath.Join(repoDir, "scripts", "sync-credentials"), Store: store, + Registry: agentRegistry, } } if cfg.Runners.GeminiEnabled() { @@ -109,6 +119,7 @@ func serve(addr, basePath string) error { ClaudeConfigDir: claudeConfigDir, CredentialSyncCmd: filepath.Join(repoDir, "scripts", "sync-credentials"), Store: store, + Registry: agentRegistry, } } if cfg.Runners.ContainerEnabled() { @@ -122,6 +133,7 @@ func serve(addr, basePath string) error { ClaudeConfigDir: claudeConfigDir, CredentialSyncCmd: filepath.Join(repoDir, "scripts", "sync-credentials"), Store: store, + Registry: agentRegistry, } } @@ -136,7 +148,6 @@ func serve(addr, basePath string) error { logger.Info("local runner registered", "endpoint", cfg.LocalModel.Endpoint, "model", cfg.LocalModel.Model) } - pool := executor.NewPool(cfg.MaxConcurrent, runners, store, logger) pool.Classifier = &executor.Classifier{ LLM: localClient, @@ -146,6 +157,23 @@ func serve(addr, basePath string) error { pool.LLM = localClient } + // Budget accountant: gate paid providers against rolling per-provider caps. + // With no limits configured this is a no-op. + var accountant *budget.Accountant + if len(cfg.Budget.Provider5hUSD) > 0 { + window := budget.DefaultWindow + if cfg.Budget.Window != "" { + if d, derr := time.ParseDuration(cfg.Budget.Window); derr == nil { + window = d + } else { + logger.Warn("invalid budget.window; using default", "value", cfg.Budget.Window, "default", window) + } + } + accountant = budget.New(store, budget.Limits{Window: window, PerProvider: cfg.Budget.Provider5hUSD}) + pool.Budget = accountant + logger.Info("budget gating enabled", "window", window, "limits", cfg.Budget.Provider5hUSD) + } + if err := store.SeedProjects(); err != nil { logger.Error("failed to seed projects", "error", err) } @@ -154,8 +182,11 @@ func serve(addr, basePath string) error { pool.RecoverStaleQueued(context.Background()) pool.RecoverStaleBlocked() - srv := api.NewServer(store, pool, logger, cfg.ClaudeBinaryPath, cfg.GeminiBinaryPath) + srv := api.NewServer(store, pool, agentRegistry, logger, cfg.ClaudeBinaryPath, cfg.GeminiBinaryPath) srv.SetBasePath(basePath) + if accountant != nil { + srv.SetBudget(accountant) + } // Configure notifiers: combine webhook (if set) with web push. notifiers := []notify.Notifier{} @@ -185,6 +216,11 @@ func serve(addr, basePath string) error { } srv.SetGitHubWebhookConfig(cfg.WebhookSecret, cfg.Projects) + if cfg.APIToken != "" { + srv.SetAPIToken(cfg.APIToken) + logger.Info("API token configured; chatbot MCP endpoint enabled at /chatbot/mcp") + } + // Register scripts. wd, _ := os.Getwd() srv.SetScripts(api.ScriptRegistry{ @@ -238,4 +274,3 @@ func serve(addr, basePath string) error { } return nil } - |
