Files
Hermit/CMakeLists.txt
T
WHC 2fde898da0
ci / linux (clang) (push) Canceled after 0s
ci / linux (gcc) (push) Canceled after 0s
ci / sanitizers (push) Canceled after 0s
ci / musl (push) Canceled after 0s
ci / macos (push) Canceled after 0s
Usability pass: tool registry, TUI polish, saved keys and settings
Tools
- Table-driven tool registry with permission classes and default/minimal
  profiles; new tools: edit_file, list_dir, glob, grep, delete_file,
  move_file, todo_write, web_fetch, ask_user (plus read_file paging).
- Built-in Myers diff; write tools return unified diffs.
- Persistent permission rules (.agent/permissions.json), per-turn undo
  snapshots (.agent/undo), AGENTS.md project notes, auto-compact at 80%
  of the model context, cancellable run_shell with process-group kill.

TUI
- Table-driven slash commands: /provider /key /effort /status /tools
  /permissions /yolo /undo /retry /diff /export /copy /rename /delete
  /init /max-steps /todo, with / and @ completion.
- Permission modal with diff and prefix rules, ask_user modal, todo
  panel, header with model/effort/context, bracketed paste, line editing,
  wide-char cursor, NO_COLOR and ASCII fallback; Esc never quits.

Startup and configuration
- API keys are entered in the TUI (hidden) and saved per provider in
  <config>/credentials (0600); env variables are only a fallback.
- Last provider, per-provider model and reasoning effort are remembered
  in <config>/settings.json; --effort/-e and /effort send
  reasoning_effort (low|medium|high|max) to models that accept it.
- DeepSeek preset uses deepseek-flash / deepseek-v4-pro.
- Session flags --new/--continue/--resume/--list-sessions, --yolo,
  --confirm.

Compatibility and tests
- ncurses detection, macOS/Homebrew support, make install, XDG config
  dir, POSIX cleanups; Gitea CI (gcc/clang/asan/musl/macOS).
- Unit tests for diff, fs tools, registry, undo/perms and config;
  e2e fixtures multi_file_refactor and ask_or_todo; vendored cJSON.
2026-10-05 01:47:01 +08:00

202 lines
6.4 KiB
CMake

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/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)
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}")