diff options
| author | Claudomator Agent <agent@claudomator> | 2026-03-09 04:59:39 +0000 |
|---|---|---|
| committer | Claudomator Agent <agent@claudomator> | 2026-03-09 04:59:39 +0000 |
| commit | 67b8544b222392d8a01847e3d34559c23fd0cd12 (patch) | |
| tree | b7d27c83cac301dc9b902f3dda34d6a2a4da37d6 /internal | |
| parent | fc1459be18d4718f2c5f15325e1a1d07fb0b3a9e (diff) | |
api: make workspace root configurable instead of hardcoded /workspace
- Add workspaceRoot field (default "/workspace") to Server struct
- Add SetWorkspaceRoot method on Server
- Update handleListWorkspaces to use s.workspaceRoot
- Add WorkspaceRoot field to Config with default "/workspace"
- Wire cfg.WorkspaceRoot into server in serve.go
- Expose --workspace-root flag on the serve command
- Add TestListWorkspaces_UsesConfiguredRoot integration test
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/api/server.go | 11 | ||||
| -rw-r--r-- | internal/api/server_test.go | 44 | ||||
| -rw-r--r-- | internal/cli/serve.go | 8 | ||||
| -rw-r--r-- | internal/config/config.go | 2 |
4 files changed, 63 insertions, 2 deletions
diff --git a/internal/api/server.go b/internal/api/server.go index 1d87b3f..0868295 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -42,6 +42,7 @@ type Server struct { validateCmdPath string // overrides claudeBinPath for validate; used in tests scripts ScriptRegistry // optional; maps endpoint name → script path workDir string // working directory injected into elaborate system prompt + workspaceRoot string // root directory for listing workspaces; defaults to "/workspace" notifier notify.Notifier apiToken string // if non-empty, required for WebSocket (and REST) connections elaborateLimiter *ipRateLimiter // per-IP rate limiter for elaborate/validate endpoints @@ -57,6 +58,11 @@ func (s *Server) SetNotifier(n notify.Notifier) { s.notifier = n } +// SetWorkspaceRoot configures the root directory used by handleListWorkspaces. +func (s *Server) SetWorkspaceRoot(path string) { + s.workspaceRoot = path +} + func NewServer(store *storage.DB, pool *executor.Pool, logger *slog.Logger, claudeBinPath, geminiBinPath string) *Server { wd, _ := os.Getwd() s := &Server{ @@ -71,6 +77,7 @@ func NewServer(store *storage.DB, pool *executor.Pool, logger *slog.Logger, clau claudeBinPath: claudeBinPath, geminiBinPath: geminiBinPath, workDir: wd, + workspaceRoot: "/workspace", } s.routes() return s @@ -321,7 +328,7 @@ func (s *Server) handleListWorkspaces(w http.ResponseWriter, r *http.Request) { } } - entries, err := os.ReadDir("/workspace") + entries, err := os.ReadDir(s.workspaceRoot) if err != nil { writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "failed to list workspaces"}) return @@ -329,7 +336,7 @@ func (s *Server) handleListWorkspaces(w http.ResponseWriter, r *http.Request) { var dirs []string for _, e := range entries { if e.IsDir() { - dirs = append(dirs, "/workspace/"+e.Name()) + dirs = append(dirs, s.workspaceRoot+"/"+e.Name()) } } writeJSON(w, http.StatusOK, dirs) diff --git a/internal/api/server_test.go b/internal/api/server_test.go index 8484e02..765b813 100644 --- a/internal/api/server_test.go +++ b/internal/api/server_test.go @@ -99,6 +99,50 @@ func (m *mockRunner) Run(_ context.Context, _ *task.Task, _ *storage.Execution) return nil } +func TestListWorkspaces_UsesConfiguredRoot(t *testing.T) { + srv, _ := testServer(t) + + root := t.TempDir() + for _, name := range []string{"alpha", "beta", "gamma"} { + if err := os.Mkdir(filepath.Join(root, name), 0755); err != nil { + t.Fatal(err) + } + } + // Also create a file (should be excluded from results). + f, err := os.Create(filepath.Join(root, "notadir.txt")) + if err != nil { + t.Fatal(err) + } + f.Close() + + srv.SetWorkspaceRoot(root) + + req := httptest.NewRequest("GET", "/api/workspaces", nil) + w := httptest.NewRecorder() + srv.Handler().ServeHTTP(w, req) + + if w.Code != http.StatusOK { + t.Fatalf("status: want 200, got %d", w.Code) + } + var dirs []string + if err := json.NewDecoder(w.Body).Decode(&dirs); err != nil { + t.Fatalf("decode: %v", err) + } + want := map[string]bool{ + root + "/alpha": true, + root + "/beta": true, + root + "/gamma": true, + } + if len(dirs) != len(want) { + t.Fatalf("want %d dirs, got %d: %v", len(want), len(dirs), dirs) + } + for _, d := range dirs { + if !want[d] { + t.Errorf("unexpected dir in response: %s", d) + } + } +} + func TestHealthEndpoint(t *testing.T) { srv, _ := testServer(t) req := httptest.NewRequest("GET", "/api/health", nil) diff --git a/internal/cli/serve.go b/internal/cli/serve.go index 4253d56..e5bd873 100644 --- a/internal/cli/serve.go +++ b/internal/cli/serve.go @@ -20,16 +20,21 @@ import ( func newServeCmd() *cobra.Command { var addr string + var workspaceRoot string cmd := &cobra.Command{ Use: "serve", Short: "Start the Claudomator API server", RunE: func(cmd *cobra.Command, args []string) error { + if cmd.Flags().Changed("workspace-root") { + cfg.WorkspaceRoot = workspaceRoot + } return serve(addr) }, } cmd.Flags().StringVar(&addr, "addr", ":8484", "listen address") + cmd.Flags().StringVar(&workspaceRoot, "workspace-root", "/workspace", "root directory for listing workspaces") return cmd } @@ -77,6 +82,9 @@ func serve(addr string) error { if cfg.WebhookURL != "" { srv.SetNotifier(notify.NewWebhookNotifier(cfg.WebhookURL, logger)) } + if cfg.WorkspaceRoot != "" { + srv.SetWorkspaceRoot(cfg.WorkspaceRoot) + } // Register scripts. wd, _ := os.Getwd() diff --git a/internal/config/config.go b/internal/config/config.go index d3d9d68..daf42fe 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -17,6 +17,7 @@ type Config struct { DefaultTimeout string `toml:"default_timeout"` ServerAddr string `toml:"server_addr"` WebhookURL string `toml:"webhook_url"` + WorkspaceRoot string `toml:"workspace_root"` } func Default() (*Config, error) { @@ -37,6 +38,7 @@ func Default() (*Config, error) { MaxConcurrent: 3, DefaultTimeout: "15m", ServerAddr: ":8484", + WorkspaceRoot: "/workspace", }, nil } |
