Repository navigation
Expand file tree
/
Copy pathMakefile
More file actions
476 lines (429 loc) · 20.3 KB
/
Copy pathMakefile
File metadata and controls
476 lines (429 loc) · 20.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
SHELL=/usr/bin/env bash
# Makefile for OpenHands project
# Variables
BACKEND_HOST ?= "127.0.0.1"
BACKEND_PORT ?= 3000
BACKEND_HOST_PORT = "$(BACKEND_HOST):$(BACKEND_PORT)"
BACKEND_START_TIMEOUT ?= 90
FRONTEND_HOST ?= "127.0.0.1"
FRONTEND_PORT ?= 3001
DEFAULT_WORKSPACE_DIR = "./workspace"
DEFAULT_MODEL = "gpt-4o"
CONFIG_FILE = config.toml
PRE_COMMIT_CONFIG_PATH = "./dev_config/python/.pre-commit-config.yaml"
PYTHON_MIN_VERSION = 3.12
PYTHON_MAX_VERSION = 3.14
PYTHON_CANDIDATES ?= python3.13 python3.12 python3
PYTHON ?= $(shell for cmd in $(PYTHON_CANDIDATES); do \
if command -v $$cmd > /dev/null 2>&1 && $$cmd -c 'import sys; raise SystemExit(0 if ((3, 12) <= sys.version_info[:2] < (3, 14)) else 1)' > /dev/null 2>&1; then \
echo $$cmd; \
exit 0; \
fi; \
done)
KIND_CLUSTER_NAME = "local-hands"
# Local development database. `make local-db` runs PostgreSQL in a container
# using these values; `make run` / `make start-backend` connect with them.
# Override any of them to point at a PostgreSQL server you manage yourself.
DB_HOST ?= 127.0.0.1
DB_PORT ?= 5432
DB_NAME ?= openhands
DB_USER ?= postgres
DB_PASS ?= postgres
LOCAL_DB_CONTAINER ?= openhands-postgres
# A named volume, so deleting the container does not take the data with it.
# `make reset-db` is the only thing that removes it.
LOCAL_DB_VOLUME ?= openhands-postgres-data
# Matches the image the test suite uses (tests/postgres_testdb.py).
LOCAL_DB_IMAGE ?= postgres:16
# Applied only to the targets that talk to the local database, so the SaaS
# targets keep reading their own environment.
LOCAL_DB_ENV = DB_HOST=$(DB_HOST) DB_PORT=$(DB_PORT) DB_NAME=$(DB_NAME) DB_USER=$(DB_USER) DB_PASS=$(DB_PASS)
# ANSI color codes
GREEN=$(shell tput -Txterm setaf 2)
YELLOW=$(shell tput -Txterm setaf 3)
RED=$(shell tput -Txterm setaf 1)
BLUE=$(shell tput -Txterm setaf 6)
RESET=$(shell tput -Txterm sgr0)
# Build
# Set AGENT_CANVAS=1 to also build the Agent Canvas bundle (network access
# required; see build-agent-canvas below). Off by default so the CI build stays
# hermetic.
build:
@echo "$(GREEN)Building project...$(RESET)"
@$(MAKE) -s check-dependencies
@$(MAKE) -s install-python-dependencies
@$(MAKE) -s install-frontend-dependencies
@$(MAKE) -s install-pre-commit-hooks
ifeq ($(AGENT_CANVAS),1)
@$(MAKE) -s build-agent-canvas
endif
@$(MAKE) -s build-frontend
@echo "$(GREEN)Build completed successfully.$(RESET)"
check-dependencies:
@echo "$(YELLOW)Checking dependencies...$(RESET)"
@$(MAKE) -s check-system
@$(MAKE) -s check-python
@$(MAKE) -s check-npm
@$(MAKE) -s check-nodejs
ifeq ($(INSTALL_DOCKER),)
@$(MAKE) -s check-docker
endif
@$(MAKE) -s check-uv
@$(MAKE) -s check-tmux
@echo "$(GREEN)Dependencies checked successfully.$(RESET)"
check-system:
@echo "$(YELLOW)Checking system...$(RESET)"
@if [ "$(shell uname)" = "Darwin" ]; then \
echo "$(BLUE)macOS detected.$(RESET)"; \
elif [ "$(shell uname)" = "Linux" ]; then \
if [ -f "/etc/manjaro-release" ]; then \
echo "$(BLUE)Manjaro Linux detected.$(RESET)"; \
else \
echo "$(BLUE)Linux detected.$(RESET)"; \
fi; \
elif [ "$$(uname -r | grep -i microsoft)" ]; then \
echo "$(BLUE)Windows Subsystem for Linux detected.$(RESET)"; \
else \
echo "$(RED)Unsupported system detected. Please use macOS, Linux, or Windows Subsystem for Linux (WSL).$(RESET)"; \
exit 1; \
fi
check-python:
@echo "$(YELLOW)Checking Python installation...$(RESET)"
@if [ -n "$(PYTHON)" ]; then \
echo "$(BLUE)$$($(PYTHON) --version) is already installed (using $(PYTHON)).$(RESET)"; \
else \
echo "$(RED)A compatible Python interpreter (>= $(PYTHON_MIN_VERSION), < $(PYTHON_MAX_VERSION)) is required. Please install Python 3.12 or 3.13 to continue.$(RESET)"; \
exit 1; \
fi
check-npm:
@echo "$(YELLOW)Checking npm installation...$(RESET)"
@if command -v npm > /dev/null; then \
echo "$(BLUE)npm $(shell npm --version) is already installed.$(RESET)"; \
else \
echo "$(RED)npm is not installed. Please install Node.js to continue.$(RESET)"; \
exit 1; \
fi
check-nodejs:
@echo "$(YELLOW)Checking Node.js installation...$(RESET)"
@if command -v node > /dev/null; then \
NODE_VERSION=$(shell node --version | sed -E 's/v//g'); \
IFS='.' read -r -a NODE_VERSION_ARRAY <<< "$$NODE_VERSION"; \
if [ "$${NODE_VERSION_ARRAY[0]}" -ge 22 ]; then \
echo "$(BLUE)Node.js $$NODE_VERSION is already installed.$(RESET)"; \
else \
echo "$(RED)Node.js 22.x or later is required. Please install Node.js 22.x or later to continue.$(RESET)"; \
exit 1; \
fi; \
else \
echo "$(RED)Node.js is not installed. Please install Node.js to continue.$(RESET)"; \
exit 1; \
fi
check-docker:
@echo "$(YELLOW)Checking Docker installation...$(RESET)"
@if command -v docker > /dev/null; then \
echo "$(BLUE)$(shell docker --version) is already installed.$(RESET)"; \
else \
echo "$(RED)Docker is not installed. Please install Docker to continue.$(RESET)"; \
exit 1; \
fi
check-tmux:
@echo "$(YELLOW)Checking tmux installation...$(RESET)"
@if command -v tmux > /dev/null; then \
echo "$(BLUE)$(shell tmux -V) is already installed.$(RESET)"; \
else \
echo "$(YELLOW)╔════════════════════════════════════════════════════════════════════════════╗$(RESET)"; \
echo "$(YELLOW)║ OPTIONAL: tmux is not installed. ║$(RESET)"; \
echo "$(YELLOW)║ Some advanced terminal features may not work without tmux. ║$(RESET)"; \
echo "$(YELLOW)║ You can install it if needed, but it's not required for development. ║$(RESET)"; \
echo "$(YELLOW)╚════════════════════════════════════════════════════════════════════════════╝$(RESET)"; \
fi
check-uv:
@echo "$(YELLOW)Checking uv installation...$(RESET)"
@if command -v uv > /dev/null; then \
echo "$(BLUE)$$(uv --version) is already installed.$(RESET)"; \
else \
echo "$(RED)uv is not installed. You can install uv by running one of the following commands:"; \
echo "$(RED) curl -LsSf https://astral.sh/uv/install.sh | sh$(RESET)"; \
echo "$(RED) brew install uv$(RESET)"; \
echo "$(RED)More detail here: https://docs.astral.sh/uv/getting-started/installation/$(RESET)"; \
exit 1; \
fi
install-python-dependencies: check-python check-uv
@echo "$(GREEN)Installing Python dependencies...$(RESET)"
@if [ -z "${TZ}" ]; then \
echo "Defaulting TZ (timezone) to UTC"; \
export TZ="UTC"; \
fi
uv sync --python $(PYTHON) --all-groups
@if [ "${INSTALL_PLAYWRIGHT}" != "false" ] && [ "${INSTALL_PLAYWRIGHT}" != "0" ]; then \
if [ -f "/etc/manjaro-release" ]; then \
echo "$(BLUE)Detected Manjaro Linux. Installing Playwright dependencies...$(RESET)"; \
uv run playwright install chromium; \
else \
if [ ! -f cache/playwright_chromium_is_installed.txt ]; then \
echo "Running playwright install --with-deps chromium..."; \
uv run playwright install --with-deps chromium; \
mkdir -p cache; \
touch cache/playwright_chromium_is_installed.txt; \
else \
echo "Setup already done. Skipping playwright installation."; \
fi \
fi \
else \
echo "Skipping Playwright installation (INSTALL_PLAYWRIGHT=${INSTALL_PLAYWRIGHT})."; \
fi
@echo "$(GREEN)Python dependencies installed successfully.$(RESET)"
install-frontend-dependencies: check-npm check-nodejs
@echo "$(YELLOW)Setting up frontend environment...$(RESET)"
@echo "$(YELLOW)Detect Node.js version...$(RESET)"
@cd frontend && node ./scripts/detect-node-version.js
echo "$(BLUE)Installing frontend dependencies with npm...$(RESET)"
@cd frontend && npm install
@echo "$(GREEN)Frontend dependencies installed successfully.$(RESET)"
install-pre-commit-hooks: check-python check-uv install-python-dependencies
@echo "$(YELLOW)Installing pre-commit hooks...$(RESET)"
@git config --unset-all core.hooksPath || true
@uv run pre-commit install --config $(PRE_COMMIT_CONFIG_PATH)
@echo "$(GREEN)Pre-commit hooks installed successfully.$(RESET)"
lint-backend: install-pre-commit-hooks
@echo "$(YELLOW)Running linters...$(RESET)"
@uv run pre-commit run --all-files --show-diff-on-failure --config $(PRE_COMMIT_CONFIG_PATH)
lint-frontend: install-frontend-dependencies
@echo "$(YELLOW)Running linters for frontend...$(RESET)"
@cd frontend && npm run lint
lint:
@$(MAKE) -s lint-frontend
@$(MAKE) -s lint-backend
kind:
@echo "$(YELLOW)Checking if kind is installed...$(RESET)"
@if ! command -v kind > /dev/null; then \
echo "$(RED)kind is not installed. Please install kind with `brew install kind` to continue$(RESET)"; \
exit 1; \
else \
echo "$(BLUE)kind $(shell kind version) is already installed.$(RESET)"; \
fi
@echo "$(YELLOW)Checking if kind cluster '$(KIND_CLUSTER_NAME)' already exists...$(RESET)"
@if kind get clusters | grep -q "^$(KIND_CLUSTER_NAME)$$"; then \
echo "$(BLUE)Kind cluster '$(KIND_CLUSTER_NAME)' already exists.$(RESET)"; \
kubectl config use-context kind-$(KIND_CLUSTER_NAME); \
else \
echo "$(YELLOW)Creating kind cluster '$(KIND_CLUSTER_NAME)'...$(RESET)"; \
kind create cluster --name $(KIND_CLUSTER_NAME) --config kind/cluster.yaml; \
fi
@echo "$(YELLOW)Checking if mirrord is installed...$(RESET)"
@if ! command -v mirrord > /dev/null; then \
echo "$(RED)mirrord is not installed. Please install mirrord with `brew install metalbear-co/mirrord/mirrord` to continue$(RESET)"; \
exit 1; \
else \
echo "$(BLUE)mirrord $(shell mirrord --version) is already installed.$(RESET)"; \
fi
@echo "$(YELLOW)Installing k8s mirrord resources...$(RESET)"
@kubectl apply -f kind/manifests
@echo "$(GREEN)Mirrord resources installed successfully.$(RESET)"
@echo "$(YELLOW)Waiting for Mirrord pod to be ready.$(RESET)"
@sleep 5
@kubectl wait --for=condition=Available deployment/ubuntu-dev
@echo "$(YELLOW)Waiting for Nginx to be ready.$(RESET)"
@kubectl -n ingress-nginx wait --for=condition=Available deployment/ingress-nginx-controller
@echo "$(YELLOW)Running make run-saas inside of mirrord.$(RESET)"
@mirrord exec --target deployment/ubuntu-dev -- make run-saas
test-frontend:
@echo "$(YELLOW)Running tests for frontend...$(RESET)"
@cd frontend && npm run test
test:
@$(MAKE) -s test-frontend
build-frontend:
@echo "$(YELLOW)Building frontend...$(RESET)"
@cd frontend && npm run prepare && npm run build
# Build the Agent Canvas SPA (OpenHands/OpenHands) into frontend/public/canvas
# so it is served at /canvas. In cloud /canvas is a separate service behind an
# ingress rule; locally there is no ingress, so the bundle is baked in here.
#
# Deliberately NOT part of `build`: `make build` is a required CI step
# (.github/workflows/py-tests.yml) and cloning+building a second repository
# there would add a network dependency and a new failure mode to a job that
# currently works. Only the local run targets need /canvas.
#
# Fail-soft on purpose: /canvas is scaffolding over a still-working OSS
# frontend, so a canvas build failure (e.g. no network) must not stop the app
# from starting. The backend and dev server both no-op when the bundle is
# absent.
# Temporary: when the OSS frontend is retired, /canvas is the only surface.
build-agent-canvas:
@echo "$(YELLOW)Building Agent Canvas SPA...$(RESET)"
@cd frontend && npm run build:agent-canvas \
|| echo "$(YELLOW)Agent Canvas build failed; continuing without /canvas.$(RESET)"
# Install frontend deps and build the canvas bundle that `run*` serves at /canvas.
prepare-local-frontend:
@$(MAKE) -s install-frontend-dependencies
@$(MAKE) -s build-agent-canvas
# Start (or reuse) a local PostgreSQL container and migrate it to head. Run this
# once before `make run`; the app only migrates on startup when
# RUN_MIGRATIONS_ON_STARTUP=true.
local-db: check-docker
@echo "$(YELLOW)Starting local PostgreSQL ($(LOCAL_DB_CONTAINER))...$(RESET)"
@if [ -n "$$(docker ps -aq -f name=^$(LOCAL_DB_CONTAINER)$$)" ]; then \
docker start $(LOCAL_DB_CONTAINER) > /dev/null; \
else \
docker run -d --name $(LOCAL_DB_CONTAINER) \
-p $(DB_PORT):5432 \
-v $(LOCAL_DB_VOLUME):/var/lib/postgresql/data \
-e POSTGRES_USER=$(DB_USER) \
-e POSTGRES_PASSWORD=$(DB_PASS) \
-e POSTGRES_DB=$(DB_NAME) \
$(LOCAL_DB_IMAGE) > /dev/null; \
fi
@echo "$(YELLOW)Waiting for PostgreSQL to accept connections...$(RESET)"
@for i in $$(seq 1 60); do \
if docker exec $(LOCAL_DB_CONTAINER) pg_isready -U $(DB_USER) > /dev/null 2>&1; then break; fi; \
if [ $$i -eq 60 ]; then \
echo "$(RED)PostgreSQL did not become ready. Check: docker logs $(LOCAL_DB_CONTAINER)$(RESET)"; \
exit 1; \
fi; \
sleep 1; \
done
@echo "$(YELLOW)Applying migrations...$(RESET)"
@$(LOCAL_DB_ENV) uv run alembic upgrade head
@echo "$(GREEN)Local database ready on $(DB_HOST):$(DB_PORT).$(RESET)"
# Throw the local database away and build a fresh one. The data lives in a named
# volume, which `docker rm -v` leaves alone, so remove it by name.
reset-db: check-docker
@echo "$(YELLOW)Removing local PostgreSQL ($(LOCAL_DB_CONTAINER)) and its data...$(RESET)"
@docker rm -f $(LOCAL_DB_CONTAINER) > /dev/null 2>&1 || true
@docker volume rm $(LOCAL_DB_VOLUME) > /dev/null 2>&1 || true
@$(MAKE) -s local-db
# Start backend
start-backend:
@echo "$(YELLOW)Starting backend...$(RESET)"
@$(LOCAL_DB_ENV) uv run uvicorn openhands.server.listen:app --host $(BACKEND_HOST) --port $(BACKEND_PORT) --reload --reload-exclude "./workspace"
# Start the SaaS/enterprise backend (saas_server.py), which layers the enterprise
# routes on top of the app server. Needs the SaaS env (Postgres, Keycloak, ...);
# see dev_config/local_saas/README.md.
start-saas-backend:
@echo "$(YELLOW)Starting SaaS backend...$(RESET)"
@uv run uvicorn saas_server:app --host $(BACKEND_HOST) --port $(BACKEND_PORT) --reload --reload-exclude "./workspace"
# Start the background worker, which runs the app's jobs. Needs the database
# migrated to head, like the backend.
start-worker:
@echo "$(YELLOW)Starting worker...$(RESET)"
@$(LOCAL_DB_ENV) uv run python -m openhands.app_server.worker
# Start frontend
start-frontend:
@echo "$(YELLOW)Starting frontend...$(RESET)"
@cd frontend && \
if grep -qi microsoft /proc/version 2>/dev/null; then \
echo "Detected WSL environment. Using 'dev_wsl'"; \
SCRIPT=dev_wsl; \
else \
SCRIPT=dev; \
fi; \
VITE_BACKEND_HOST=$(BACKEND_HOST_PORT) VITE_FRONTEND_PORT=$(FRONTEND_PORT) npm run $$SCRIPT -- --port $(FRONTEND_PORT) --host $(BACKEND_HOST)
# Common setup for running the app (non-callable)
_run_setup:
@if [ "$(OS)" = "Windows_NT" ]; then \
echo "$(RED) Windows is not supported, use WSL instead!$(RESET)"; \
exit 1; \
fi
@mkdir -p logs
@echo "$(YELLOW)Starting backend server...$(RESET)"
@$(LOCAL_DB_ENV) uv run uvicorn openhands.server.listen:app --host $(BACKEND_HOST) --port $(BACKEND_PORT) &
@$(MAKE) -s _wait_for_backend
@echo "$(YELLOW)Starting worker...$(RESET)"
@$(LOCAL_DB_ENV) uv run python -m openhands.app_server.worker &
# Run the app (needs `make local-db`, or DB_HOST pointed at your own PostgreSQL)
run:
@echo "$(YELLOW)Running the app...$(RESET)"
@$(MAKE) -s prepare-local-frontend
@$(MAKE) -s _run_setup
@$(MAKE) -s start-frontend
@echo "$(GREEN)Application started successfully.$(RESET)"
# Run the SaaS app (SaaS backend + frontend dev server)
run-saas:
@echo "$(YELLOW)Running the SaaS app...$(RESET)"
@$(MAKE) -s prepare-local-frontend
@$(MAKE) -s _run_saas_setup
@$(MAKE) -s start-frontend
@echo "$(GREEN)Application started successfully.$(RESET)"
_run_saas_setup:
@mkdir -p logs
@echo "$(YELLOW)Starting SaaS backend server...$(RESET)"
@uv run uvicorn saas_server:app --host $(BACKEND_HOST) --port $(BACKEND_PORT) &
@$(MAKE) -s _wait_for_backend
@echo "$(YELLOW)Starting worker...$(RESET)"
@uv run python -m openhands.app_server.worker &
# Wait for the backgrounded backend to bind its port, giving up rather than
# hanging forever when it exits during startup (non-callable).
_wait_for_backend:
@echo "$(YELLOW)Waiting for the backend to start...$(RESET)"
@for i in $$(seq 1 $(BACKEND_START_TIMEOUT)); do \
if nc -z localhost $(BACKEND_PORT) 2>/dev/null; then \
echo "$(GREEN)Backend started successfully.$(RESET)"; \
exit 0; \
fi; \
sleep 1; \
done; \
echo "$(RED)Backend did not bind port $(BACKEND_PORT) within $(BACKEND_START_TIMEOUT)s. See the output above for why it exited.$(RESET)"; \
exit 1
# Setup config.toml
setup-config:
@echo "$(YELLOW)Setting up config.toml...$(RESET)"
@$(MAKE) setup-config-prompts
@mv $(CONFIG_FILE).tmp $(CONFIG_FILE)
@echo "$(GREEN)Config.toml setup completed.$(RESET)"
setup-config-prompts:
@echo "[core]" > $(CONFIG_FILE).tmp
@read -p "Enter your workspace directory (as absolute path) [default: $(DEFAULT_WORKSPACE_DIR)]: " workspace_dir; \
workspace_dir=$${workspace_dir:-$(DEFAULT_WORKSPACE_DIR)}; \
echo "workspace_base=\"$$workspace_dir\"" >> $(CONFIG_FILE).tmp
@echo "" >> $(CONFIG_FILE).tmp
@echo "[llm]" >> $(CONFIG_FILE).tmp
@read -p "Enter your LLM model name, used for running without UI. Set the model in the UI after you start the app. (see https://docs.litellm.ai/docs/providers for full list) [default: $(DEFAULT_MODEL)]: " llm_model; \
llm_model=$${llm_model:-$(DEFAULT_MODEL)}; \
echo "model=\"$$llm_model\"" >> $(CONFIG_FILE).tmp
@read -p "Enter your LLM api key: " llm_api_key; \
echo "api_key=\"$$llm_api_key\"" >> $(CONFIG_FILE).tmp
@read -p "Enter your LLM base URL [mostly used for local LLMs, leave blank if not needed - example: http://localhost:5001/v1/]: " llm_base_url; \
if [[ ! -z "$$llm_base_url" ]]; then echo "base_url=\"$$llm_base_url\"" >> $(CONFIG_FILE).tmp; fi
setup-config-basic:
@printf '%s\n' \
'[core]' \
'workspace_base="./workspace"' \
> config.toml
@echo "$(GREEN)config.toml created.$(RESET)"
# Develop in container
docker-dev:
@if [ -f /.dockerenv ]; then \
echo "Running inside a Docker container. Exiting..."; \
exit 0; \
else \
echo "$(YELLOW)Build and run in Docker $(OPTIONS)...$(RESET)"; \
./containers/dev/dev.sh $(OPTIONS); \
fi
# Clean up all caches
clean:
@echo "$(YELLOW)Cleaning up caches...$(RESET)"
@rm -rf openhands/.cache
@echo "$(GREEN)Caches cleaned up successfully.$(RESET)"
# Help
help:
@echo "$(BLUE)Usage: make [target]$(RESET)"
@echo "Targets:"
@echo " $(GREEN)build$(RESET) - Build project, including environment setup and dependencies."
@echo " $(GREEN)lint$(RESET) - Run linters on the project."
@echo " $(GREEN)setup-config$(RESET) - Setup the configuration for OpenHands by providing LLM API key,"
@echo " LLM Model name, and workspace directory."
@echo " $(GREEN)local-db$(RESET) - Start a local PostgreSQL container and migrate it to head."
@echo " $(GREEN)reset-db$(RESET) - Delete the local PostgreSQL container and its data, then recreate it."
@echo " $(GREEN)start-backend$(RESET) - Start the backend server for the OpenHands project."
@echo " $(GREEN)start-frontend$(RESET) - Start the frontend server for the OpenHands project."
@echo " $(GREEN)start-saas-backend$(RESET) - Start the SaaS/enterprise backend (saas_server.py)."
@echo " $(GREEN)start-worker$(RESET) - Start the background worker, which runs the app's jobs."
@echo " $(GREEN)run$(RESET) - Run the OpenHands application, starting both backend and frontend servers."
@echo " Needs a database: run $(GREEN)make local-db$(RESET) first."
@echo " $(GREEN)run-saas$(RESET) - Run the SaaS app, starting the SaaS backend and the frontend server."
@echo " Backend Log file will be stored in the 'logs' directory."
@echo " $(GREEN)docker-dev$(RESET) - Build and run the OpenHands application in Docker."
@echo " $(GREEN)help$(RESET) - Display this help message, providing information on available targets."
# Phony targets
.PHONY: build check-dependencies check-system check-python check-npm check-nodejs check-docker check-uv install-python-dependencies install-frontend-dependencies install-pre-commit-hooks lint-backend lint-frontend lint test-frontend test build-frontend build-agent-canvas prepare-local-frontend local-db reset-db start-backend start-saas-backend start-worker start-frontend _run_setup _run_saas_setup _wait_for_backend run run-saas setup-config setup-config-prompts setup-config-basic docker-dev clean help
.PHONY: kind