Build
* cross build switches to pkgsCross.ucrt64 (shell-win.nix); curl (Schannel),
winpthreads and ncurses 6.6 are linked statically, so the release is
cst-agent.exe + busybox.exe (busybox-w32 Unicode) with no extra DLLs
* windres embeds a manifest with activeCodePage=UTF-8 and a version resource
* ncurses 6.6 Windows console driver patches (mouse button state, A_DIM,
wide-char declarations): scripts/patches/
* scripts/bootstrap_msys2.sh builds the same release natively in MSYS2
UCRT64; PR #1's MINGW64 notes now point there
* vendor/tre-regex (musl's TRE) provides <regex.h> on MinGW
UTF-8
* paths cross the system call boundary as UTF-16 (*W APIs); console code
pages set to 65001 and restored on exit; files are read and written in
binary mode
* child output that is not valid UTF-8 is decoded from the console, OEM or
ANSI system code page
* read_file hides CR; edit_file, apply_patch and write_file keep CRLF line
endings and a UTF-8 BOM
OS layer and processes
* os_dir_* and cst_fnmatch replace the POSIX-only directory walks in
subagent, plugins, skills, undo and fs_tools; realpath / path_under and
symlink rejection work on Windows
* every child goes through CreateProcessW with its own environment block
inside a Job Object, so timeouts and Esc end the whole process tree
Sandbox (auto mode, "auto · lowil")
* run_shell runs with a restricted Low integrity token; the workspace gets
an inheritable Low label once, the sandbox TEMP is labeled Low, and the
credentials file gets a no-read-up label
* drive roots, the profile and system folders are never labeled
* network commands still ask, since Low IL does not block the network;
Access denied and MSYS2's 0xc0000142 offer a rerun outside the sandbox
* /sandbox lists labeled folders, /sandbox reset-labels restores them
TUI
* raster console fonts are switched to Consolas / NSimSun while running
* the home logo falls back to text when the console font lacks U+2580
* mouse drag copy goes to the Windows clipboard
Distribution
* scripts/install_windows.ps1 installs a Gitea release into
%LOCALAPPDATA%\Programs\cst-agent and adds it to the user PATH;
cst-agent-update.cmd reruns it; scripts/release_windows.sh uploads the
zip with the token taken from the environment
* scripts/run_windows.bat is gone; docs/README-windows.md is rewritten
Tests
* new test_lowil, test_win_utf8 and test_os_dir; tests made portable
(cst_test_util.h)
* scripts/win_test.sh and scripts/winvm/ run the suite on English and
Chinese Windows Server VMs as Administrator, a standard user and a
Chinese user name: 29/29 on all six
200 lines
6.1 KiB
Makefile
200 lines
6.1 KiB
Makefile
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/os_win.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
|
|
|
|
SRC = $(CORE_SRC) src/textsel.c 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 \
|
|
tests/test_keymap \
|
|
tests/test_textsel \
|
|
tests/test_markdown \
|
|
tests/test_shell_compact \
|
|
tests/test_skills \
|
|
tests/test_plugins \
|
|
tests/test_mcp \
|
|
tests/test_subagent \
|
|
tests/test_lowil \
|
|
tests/test_win_utf8 \
|
|
tests/test_os_dir
|
|
|
|
TEST_LIBS = -lm $(CURL_LIBS) $(PTHREAD_LIBS)
|
|
|
|
.PHONY: all clean check demo sashidown-lib FORCE 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: $(SD_DIR)/libsashidown.a
|
|
|
|
# The sub-make decides whether the archive is stale; FORCE only makes sure it
|
|
# is asked, and -j builds wait for it before linking.
|
|
$(SD_DIR)/libsashidown.a: FORCE
|
|
$(MAKE) -C $(SD_DIR) libsashidown.a
|
|
|
|
FORCE:
|
|
|
|
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_textsel: tests/test_textsel.c src/textsel.c src/os_compat.c src/os_win.c src/util.c include/cst_textsel.h
|
|
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ tests/test_textsel.c src/textsel.c src/os_compat.c src/os_win.c src/util.c
|
|
|
|
tests/test_markdown: tests/test_markdown.c $(SD_DIR)/libsashidown.a
|
|
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ tests/test_markdown.c $(LDFLAGS) -lsashidown -lm
|
|
|
|
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"
|