CC ?= gcc CFLAGS ?= -std=c11 -Wall -Wextra -O2 -g SD_DIR := vendor/sashidown CJSON_DIR := vendor/cjson PKG_CONFIG ?= pkg-config PREFIX ?= /usr/local DESTDIR ?= UNAME_S := $(shell uname -s 2>/dev/null) UNAME_O := $(shell uname -o 2>/dev/null) # Android (Termux): pthreads live in libc. PTHREAD_LIBS := -lpthread ifeq ($(UNAME_O),Android) PTHREAD_LIBS := endif # macOS: Homebrew ncurses/curl are keg-only, so point pkg-config at them. # (macOS ships GNU make 3.81, whose $(shell) ignores exported variables.) PKG_ENV := ifeq ($(UNAME_S),Darwin) BREW_PREFIX := $(shell brew --prefix 2>/dev/null) ifneq ($(BREW_PREFIX),) PKG_ENV := PKG_CONFIG_PATH="$(BREW_PREFIX)/opt/ncurses/lib/pkgconfig:$(BREW_PREFIX)/opt/curl/lib/pkgconfig:$$PKG_CONFIG_PATH" endif CPPFLAGS += -D_DARWIN_C_SOURCE endif PKGC = $(PKG_ENV) $(PKG_CONFIG) # Wide-character curses: ncursesw, else ncurses (macOS/BSD build it wide). CURSES_PC := $(shell for p in ncursesw ncurses; do $(PKGC) --exists $$p 2>/dev/null && { echo $$p; exit 0; }; done) ifneq ($(CURSES_PC),) CURSES_CFLAGS := $(shell $(PKGC) --cflags $(CURSES_PC)) CURSES_LIBS := $(shell $(PKGC) --libs $(CURSES_PC)) else ifeq ($(UNAME_S),Darwin) CURSES_LIBS := -lncurses else CURSES_LIBS := -lncursesw endif CURL_CFLAGS := $(shell $(PKGC) --cflags libcurl 2>/dev/null) CURL_LIBS := $(shell $(PKGC) --libs libcurl 2>/dev/null) ifeq ($(strip $(CURL_LIBS)),) CURL_LIBS := -lcurl endif PKG_CFLAGS := $(CURL_CFLAGS) $(CURSES_CFLAGS) PKG_LIBS := $(CURL_LIBS) $(CURSES_LIBS) CPPFLAGS += -Iinclude -I$(SD_DIR)/include -I$(CJSON_DIR) -D_XOPEN_SOURCE_EXTENDED $(PKG_CFLAGS) LDFLAGS += -L$(SD_DIR) LDLIBS += -lsashidown -lm $(PKG_LIBS) $(PTHREAD_LIBS) CORE_SRC = \ $(CJSON_DIR)/cJSON.c \ src/util.c \ src/ctl_parse.c \ src/os_compat.c \ src/sandbox.c \ src/sandbox_exec.c \ src/session.c \ src/tools/tools.c \ src/tools/patch.c \ src/tools/registry.c \ src/tools/fs_tools.c \ src/tools/agent_tools.c \ src/tools/web.c \ src/tools/diff.c \ src/undo.c \ src/permissions.c \ src/settings.c \ src/llm_openai.c \ src/agent_loop.c \ src/agent_store.c \ src/json_baseline.c \ src/creds.c \ src/mm_tool.c \ src/models.c \ src/metrics.c SRC = $(CORE_SRC) src/tui.c src/main.c OBJ = $(SRC:.c=.o) HDRS = $(wildcard include/*.h) $(wildcard src/tools/*.h) $(CJSON_DIR)/cJSON.h TESTS = \ tests/test_ctl_frontier \ tests/test_ctl_heredoc \ tests/test_json_baseline \ tests/test_replay_starvation \ tests/test_replay_real \ tests/test_mm_tool \ tests/test_llm_body \ tests/test_sse_decode \ tests/test_apply_patch \ tests/test_session_roundtrip \ tests/test_shell \ tests/test_diff \ tests/test_tools_fs \ tests/test_registry \ tests/test_undo \ tests/test_config \ tests/test_models \ tests/test_sandbox TEST_LIBS = -lm $(CURL_LIBS) $(PTHREAD_LIBS) .PHONY: all clean check demo sashidown-lib cmake-gcc cmake-clang asan install uninstall all: sashidown-lib cst-agent cmake-gcc: cmake -B build-gcc -DCMAKE_C_COMPILER=gcc -DCST_BUILD_TUI=ON cmake --build build-gcc ctest --test-dir build-gcc --output-on-failure cmake-clang: cmake -B build-clang -DCMAKE_C_COMPILER=clang -DCST_BUILD_TUI=ON cmake --build build-clang sashidown-lib: $(MAKE) -C $(SD_DIR) libsashidown.a cst-agent: $(OBJ) $(SD_DIR)/libsashidown.a $(CC) $(CFLAGS) -o $@ $(OBJ) $(LDFLAGS) $(LDLIBS) %.o: %.c $(HDRS) $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $< src/tui.o: src/tui.c $(HDRS) $(SD_DIR)/include/sashidown.h $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $< tests/test_ctl_frontier: tests/test_ctl_frontier.c src/ctl_parse.c include/ctl.h $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ tests/test_ctl_frontier.c src/ctl_parse.c tests/test_ctl_heredoc: tests/test_ctl_heredoc.c src/ctl_parse.c include/ctl.h $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ tests/test_ctl_heredoc.c src/ctl_parse.c tests/test_json_baseline: tests/test_json_baseline.c src/json_baseline.c $(CJSON_DIR)/cJSON.c $(HDRS) $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ tests/test_json_baseline.c src/json_baseline.c $(CJSON_DIR)/cJSON.c -lm tests/test_replay_starvation: tests/test_replay_starvation.c src/ctl_parse.c src/json_baseline.c $(CJSON_DIR)/cJSON.c $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ tests/test_replay_starvation.c src/ctl_parse.c src/json_baseline.c $(CJSON_DIR)/cJSON.c -lm tests/test_replay_real: tests/test_replay_real.c src/ctl_parse.c src/json_baseline.c $(CJSON_DIR)/cJSON.c $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ tests/test_replay_real.c src/ctl_parse.c src/json_baseline.c $(CJSON_DIR)/cJSON.c -lm tests/test_mm_tool: tests/test_mm_tool.c src/mm_tool.c include/mm_tool.h $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ tests/test_mm_tool.c src/mm_tool.c tests/test_%: tests/test_%.c $(CORE_SRC) $(HDRS) $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $< $(CORE_SRC) $(TEST_LIBS) check: $(TESTS) @set -e; for t in $(TESTS); do echo "== $$t"; ./$$t; done # AddressSanitizer + UBSan build of the agent and all tests. asan: $(MAKE) clean $(MAKE) sashidown-lib $(MAKE) CFLAGS="-std=c11 -Wall -Wextra -O1 -g -fsanitize=address,undefined -fno-sanitize-recover=undefined -fno-omit-frame-pointer" cst-agent check demo: cst-agent ./cst-agent --demo-ctl --workspace . clean: rm -f $(OBJ) cst-agent $(TESTS) install: cst-agent install -d "$(DESTDIR)$(PREFIX)/bin" install -m 0755 cst-agent "$(DESTDIR)$(PREFIX)/bin/cst-agent" uninstall: rm -f "$(DESTDIR)$(PREFIX)/bin/cst-agent"