cmake_minimum_required(VERSION 3.16) project(cst-agent C) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) option(CST_BUILD_TUI "Build ncurses/PDCurses TUI" ON) option(CST_NATIVE_ARCH "Pass -march=native to sashidown" OFF) option(CST_BUNDLE_BUSYBOX "Install placeholder/scripts for busybox beside binary" ON) set(SD_DIR "${CMAKE_CURRENT_SOURCE_DIR}/vendor/sashidown") if(CST_NATIVE_ARCH) set(_SD_MAKE_EXTRA CST_NATIVE_ARCH=1) else() set(_SD_MAKE_EXTRA "") endif() # Separate archive for MinGW so Linux host builds keep libsashidown.a if(CMAKE_SYSTEM_NAME STREQUAL "Windows") set(SASHIDOWN_A "${SD_DIR}/libsashidown-mingw.a") set(_MINGW_AR x86_64-w64-mingw32-ar) add_custom_command( OUTPUT "${SASHIDOWN_A}" COMMAND ${CMAKE_MAKE_PROGRAM} -C "${SD_DIR}" clean COMMAND ${CMAKE_MAKE_PROGRAM} -C "${SD_DIR}" libsashidown.a CC=${CMAKE_C_COMPILER} AR=${_MINGW_AR} RANLIB=x86_64-w64-mingw32-ranlib COMMAND ${CMAKE_COMMAND} -E copy_if_different "${SD_DIR}/libsashidown.a" "${SASHIDOWN_A}" COMMAND ${CMAKE_MAKE_PROGRAM} -C "${SD_DIR}" clean COMMENT "Building libsashidown for MinGW" VERBATIM) else() set(SASHIDOWN_A "${SD_DIR}/libsashidown.a") add_custom_command( OUTPUT "${SASHIDOWN_A}" COMMAND ${CMAKE_MAKE_PROGRAM} -C "${SD_DIR}" libsashidown.a CC=${CMAKE_C_COMPILER} ${_SD_MAKE_EXTRA} COMMENT "Building libsashidown.a" VERBATIM) endif() add_custom_target(sashidown DEPENDS "${SASHIDOWN_A}") find_package(CURL REQUIRED) find_package(Threads REQUIRED) set(CJSON_DIR "${CMAKE_CURRENT_SOURCE_DIR}/vendor/cjson") set(CST_CORE_SOURCES vendor/cjson/cJSON.c src/util.c src/ctl_parse.c src/os_compat.c src/sandbox.c src/sandbox_exec.c src/keymap.c src/skills.c src/extensions.c src/plugins.c src/mcp.c src/subagent.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 ) set(CST_SOURCES ${CST_CORE_SOURCES} src/main.c) if(CST_BUILD_TUI) list(APPEND CST_SOURCES src/tui.c) else() list(APPEND CST_SOURCES src/tui_stub.c) endif() add_executable(cst-agent ${CST_SOURCES}) add_dependencies(cst-agent sashidown) target_include_directories(cst-agent PRIVATE include "${SD_DIR}/include" "${CJSON_DIR}" ) target_compile_definitions(cst-agent PRIVATE _GNU_SOURCE) if(NOT WIN32) target_compile_definitions(cst-agent PRIVATE _XOPEN_SOURCE_EXTENDED) endif() target_link_libraries(cst-agent PRIVATE "${SASHIDOWN_A}" CURL::libcurl Threads::Threads ) if(NOT WIN32) target_link_libraries(cst-agent PRIVATE m) endif() if(CST_BUILD_TUI) if(WIN32) # Prefer PDCurses (pdcurses / curses). Users may set PDCurses_ROOT. find_path(PDCURSES_INCLUDE_DIR curses.h HINTS ENV PDCurses_ROOT "${PDCurses_ROOT}" PATH_SUFFIXES include) find_library(PDCURSES_LIBRARY NAMES pdcurses pdcurses.dll curses HINTS ENV PDCurses_ROOT "${PDCurses_ROOT}" PATH_SUFFIXES lib) if(PDCURSES_INCLUDE_DIR AND PDCURSES_LIBRARY) target_include_directories(cst-agent PRIVATE ${PDCURSES_INCLUDE_DIR}) target_link_libraries(cst-agent PRIVATE ${PDCURSES_LIBRARY}) target_compile_definitions(cst-agent PRIVATE CST_USE_PDCURSES=1) message(STATUS "TUI: PDCurses (${PDCURSES_LIBRARY})") else() message(FATAL_ERROR "CST_BUILD_TUI=ON on Windows requires PDCurses (set PDCurses_ROOT)") endif() else() set(CURSES_NEED_WIDE TRUE) find_package(Curses) if(CURSES_FOUND) target_include_directories(cst-agent PRIVATE ${CURSES_INCLUDE_DIRS}) target_link_libraries(cst-agent PRIVATE ${CURSES_LIBRARIES}) else() # Fallback common on Linux distros target_link_libraries(cst-agent PRIVATE ncursesw) endif() target_compile_definitions(cst-agent PRIVATE CST_USE_NCURSES=1) endif() else() target_compile_definitions(cst-agent PRIVATE CST_HEADLESS_ONLY=1) endif() if(CMAKE_SYSTEM_NAME STREQUAL "Windows" OR WIN32) # curl Schannel + networking target_link_libraries(cst-agent PRIVATE ws2_32 crypt32 secur32 bcrypt advapi32) endif() # --- tests --- enable_testing() set(_CST_SMALL_TESTS test_ctl_frontier test_ctl_heredoc test_json_baseline test_replay_starvation test_replay_real test_mm_tool) foreach(t ${_CST_SMALL_TESTS}) if(t STREQUAL test_ctl_frontier OR t STREQUAL test_ctl_heredoc) add_executable(${t} tests/${t}.c src/ctl_parse.c) elseif(t STREQUAL test_json_baseline) add_executable(${t} tests/${t}.c src/json_baseline.c vendor/cjson/cJSON.c) elseif(t STREQUAL test_replay_starvation OR t STREQUAL test_replay_real) add_executable(${t} tests/${t}.c src/ctl_parse.c src/json_baseline.c vendor/cjson/cJSON.c) else() add_executable(${t} tests/test_mm_tool.c src/mm_tool.c) endif() target_include_directories(${t} PRIVATE include "${CJSON_DIR}") target_compile_definitions(${t} PRIVATE _GNU_SOURCE) if(NOT WIN32) target_link_libraries(${t} PRIVATE m) endif() add_test(NAME ${t} COMMAND ${t} WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") endforeach() # Tests that link the whole core (no TUI). foreach(t test_llm_body test_sse_decode test_apply_patch test_session_roundtrip test_shell test_diff test_tools_fs test_registry test_undo test_config test_models test_sandbox test_keymap test_shell_compact test_skills test_plugins test_mcp test_subagent) add_executable(${t} tests/${t}.c ${CST_CORE_SOURCES}) target_include_directories(${t} PRIVATE include "${CJSON_DIR}") target_compile_definitions(${t} PRIVATE _GNU_SOURCE) target_link_libraries(${t} PRIVATE CURL::libcurl Threads::Threads) if(NOT WIN32) target_link_libraries(${t} PRIVATE m) else() target_link_libraries(${t} PRIVATE ws2_32 crypt32 secur32 bcrypt advapi32) endif() add_test(NAME ${t} COMMAND ${t} WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") endforeach() # --- busybox bundle helpers --- if(CST_BUNDLE_BUSYBOX) install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/third_party/busybox/README.md" DESTINATION . OPTIONAL) if(WIN32 AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/third_party/busybox/busybox.exe") install(PROGRAMS "${CMAKE_CURRENT_SOURCE_DIR}/third_party/busybox/busybox.exe" DESTINATION .) endif() endif() if(WIN32) install(TARGETS cst-agent RUNTIME DESTINATION .) else() include(GNUInstallDirs) install(TARGETS cst-agent RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) endif() message(STATUS "Compiler: ${CMAKE_C_COMPILER_ID} ${CMAKE_C_COMPILER}") message(STATUS "CST_BUILD_TUI=${CST_BUILD_TUI}")