From 2ffe74b37c869c5c047841efa8217f75eaddf576 Mon Sep 17 00:00:00 2001 From: WHC Date: Sun, 11 Oct 2026 18:44:31 +0800 Subject: [PATCH] desktop: the window installs itself (launcher entry, icon, default.nix) app/linux/cst-agent-app.{desktop,svg} plus one CMake component ("app") in app/CMakeLists.txt: `cmake --install --component app` puts the binary in bin/, the entry in share/applications/ and the icon in hicolor/scalable/apps/. The entry's file name stays the app_id Qt sets (QGuiApplication::applicationName), or the launcher draws a generic icon; only the front end installs, the CLI keeps coming from scripts/bootstrap_linux.sh. default.nix is that install for nix users: nixpkgs' Qt 6 (qtwayland for a Wayland session, qtsvg for Breeze's SVG icons), wrapQtAppsHook so the plugins and QML modules resolve outside a nix-shell and survive nix-collect-garbage, a filtered src (third_party/ and the build directories are gigabytes this build never reads), and the version read from include/cst_version.h. doCheck runs ctest minus test_sandbox, whose Landlock assertion cannot hold inside the nix build sandbox (the other 40 pass there; make check runs all 41). Verified: nix-build, nix profile add --file ., then --selftest and --uitest in a clean environment (no nix-shell, no XDG_* from the session). --- .gitignore | 3 + README.md | 15 +++++ app/CMakeLists.txt | 21 +++++++ app/README.md | 26 +++++++++ app/linux/cst-agent-app.desktop | 15 +++++ app/linux/cst-agent-app.svg | 12 ++++ default.nix | 99 +++++++++++++++++++++++++++++++++ 7 files changed, 191 insertions(+) create mode 100644 app/linux/cst-agent-app.desktop create mode 100644 app/linux/cst-agent-app.svg create mode 100644 default.nix diff --git a/.gitignore b/.gitignore index 1bb804d..30c46c0 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,6 @@ app/android/local.properties # the engine as a static library (app/ links it; built by `make`) libcstcore.a + +# nix-build +result diff --git a/README.md b/README.md index a009f1c..a8b5d80 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,21 @@ cmake -B build-hd -DCST_BUILD_TUI=OFF && cmake --build build-hd ``` 依赖:`gcc`、`clang` 或 `tcc`(0.9.27 mob 以上)、`libcurl`、TUI 需 `ncursesw`(Windows 版用打过补丁的 ncurses 6.6,脚本自动编译)。 +### 桌面 GUI(Qt/QML,可选) + +把引擎放进同一个进程的窗口前端(无端口、无第二个进程):`app/`,细节见 [app/README.md](app/README.md)。 + +```bash +# NixOS / nix:装进用户 profile(PATH + 菜单项 + 图标),卸载 nix profile remove cst-agent +nix profile add --file . + +# 其他发行版(需要 Qt 6 的 Quick/Qml/QuickControls2 与 libcurl) +cmake -B build-app -DCST_BUILD_APP=ON -DCMAKE_BUILD_TYPE=Release +cmake --build build-app -j && cmake --install build-app --component app --prefix ~/.local +``` + +菜单里启动时工作区=启动目录(即 `$HOME`);在项目目录里 `cst-agent-app` 启动则工作区就是那个目录。 + Sashidown(`vendor/sashidown`)与 cJSON(`vendor/cjson`,MIT)已随仓库提供。 API Key:TUI 里输入并按 provider 保存到配置目录的 `credentials`(权限 0600);`/key` 更换,`--ask-key` 在命令行输入。 `--api-key` 只用于本次进程、不落盘;`DEEPSEEK_API_KEY` 等环境变量和 `CST_API_KEY` 仅作为没有保存 key 时的兜底(方便 CI)。 diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index b172171..9482359 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -14,6 +14,10 @@ project(cst-agent-app LANGUAGES C CXX VERSION 0.1.0) # Inside the repo build (the parent defines the cstcore target, shared with the CLI): # cmake -B build -DCST_BUILD_APP=ON && cmake --build build --target cst-agent-app # +# Desktop install (the window, its launcher entry and the icon; the CLI has +# its own installer) -- default.nix installs exactly this component: +# cmake --install build --component app --prefix ~/.local +# # Extra QML import paths, e.g. a Kirigami installed away from the Qt prefix: # -DCST_APP_QML_IMPORT=... (added to QML2_IMPORT_PATH / the app's runtime) @@ -119,6 +123,23 @@ if(CST_APP_GUI) set_target_properties(cst-agent-app PROPERTIES WIN32_EXECUTABLE TRUE) endif() + # A desktop install. The icon goes where the theme layout says, so the + # entry's Icon= name finds it, and the entry's file name is the app_id Qt + # sets (QGuiApplication::applicationName) -- rename one and the launcher + # loses the icon. All of it in the "app" component: the CLI keeps coming + # from scripts/bootstrap_linux.sh, and two `cst-agent` in PATH is how a + # version nobody meant to run gets run. + if(NOT ANDROID) + include(GNUInstallDirs) + install(TARGETS cst-agent-app + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT app) + install(FILES linux/cst-agent-app.desktop + DESTINATION ${CMAKE_INSTALL_DATADIR}/applications COMPONENT app) + install(FILES linux/cst-agent-app.svg + DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps + COMPONENT app) + endif() + # --- Android (APK) ----------------------------------------------------- # The engine is compiled in (top-level CMakeLists: -DCST_BUILD_TUI=OFF, # -DCST_BUILD_WEBUI=OFF, so src/webui_stub.c stands in for the server and diff --git a/app/README.md b/app/README.md index 2598ef1..478e54e 100644 --- a/app/README.md +++ b/app/README.md @@ -45,6 +45,31 @@ navigation drawer) so swapping in `org.kde.kirigami` later is a QML-only change. Point the app at a Kirigami outside the Qt prefix with `-DCST_APP_QML_IMPORT=/path/to/qml`. +## Install (Linux desktop) + +The window installs itself: the binary, a launcher entry and the icon the +launcher draws for it. All three are one CMake component (`app`), so the CLI +keeps coming from `scripts/bootstrap_linux.sh` and the profile never holds two +`cst-agent` of different vintages; the files live in `app/linux/`. + +```sh +# NixOS / nix users: PATH + menu entry + icon. The Qt apps hook bakes the +# plugin and QML module paths into the wrapper, so the app runs outside a +# nix-shell and nix-collect-garbage cannot pull Qt out from under it. +nix-build # ./result/bin/cst-agent-app, run it in place +nix profile add --file . # or: ./result/bin/cst-agent-app +nix profile remove cst-agent # uninstall + +# anywhere with Qt 6 (Quick/Qml/QuickControls2) and libcurl: +cmake -B build-app -DCST_BUILD_APP=ON -DCMAKE_BUILD_TYPE=Release +cmake --build build-app -j +cmake --install build-app --component app --prefix ~/.local +``` + +Launched from a menu, the workspace is the directory the app starts in +(`$HOME` there), so sessions land in `~/.agent/sessions`; pass +`--workspace ` to pin another one. + ## Why in one process The engine is a library, not a daemon: `libcstcore.a` (Makefile) / the @@ -408,6 +433,7 @@ so nothing about it is visible when the app is idle. file card's open/share through the app's FileProvider - [x] a turn keeps running with the app off screen (Android foreground service + wake lock; nothing to do on a desktop) +- [x] desktop install: launcher entry + icon, `nix profile add --file .` - [ ] a real turn driven through the window (needs a provider key) - [ ] SAF file picker, the back gesture, the icon glyphs on the phone - [ ] Kirigami (the rest of the Breeze look: colors, controls), tray, diff --git a/app/linux/cst-agent-app.desktop b/app/linux/cst-agent-app.desktop new file mode 100644 index 0000000..a17f8c2 --- /dev/null +++ b/app/linux/cst-agent-app.desktop @@ -0,0 +1,15 @@ +[Desktop Entry] +Type=Application +Name=CST-agent +GenericName=Local coding agent +Comment=The engine in a window: workspace is the directory it is started in +Exec=cst-agent-app +TryExec=cst-agent-app +Icon=cst-agent-app +Terminal=false +StartupNotify=true +# Qt puts the application name in the Wayland app_id / WM_CLASS, so this has to +# stay the file's own name or the launcher draws a generic icon. +StartupWMClass=cst-agent-app +Categories=Development;Utility; +Keywords=agent;ai;assistant;code; diff --git a/app/linux/cst-agent-app.svg b/app/linux/cst-agent-app.svg new file mode 100644 index 0000000..61b2cf0 --- /dev/null +++ b/app/linux/cst-agent-app.svg @@ -0,0 +1,12 @@ + + + + + + + diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..1ed9de7 --- /dev/null +++ b/default.nix @@ -0,0 +1,99 @@ +# NixOS and other nix users: the desktop front end (Qt Quick) with the engine +# it embeds, built and installed the nix way. +# +# nix-build # ./result/bin/cst-agent-app, run it in place +# nix-env -f . -i cst-agent # into the user profile: PATH, menu entry, icon +# +# The Qt apps hook is what makes the installed binary find Qt's plugins and +# QML modules outside a nix-shell (this is also what keeps them out of +# nix-collect-garbage's reach). Only the window is installed: the CLI has its +# own installer, scripts/bootstrap_linux.sh. +{ pkgs ? import { } }: + +let + # The version lives in one place: the header the engine and the APK read. + vLine = re: + let + hit = pkgs.lib.findFirst + (l: builtins.match "#define ${re} ([0-9]+)" l != null) "" + (pkgs.lib.splitString "\n" (builtins.readFile ./include/cst_version.h)); + in + builtins.head (builtins.match "#define ${re} ([0-9]+)" hit); +in +pkgs.stdenv.mkDerivation { + pname = "cst-agent"; + version = + "${vLine "CST_VERSION_MAJOR"}.${vLine "CST_VERSION_MINOR"}.${vLine "CST_VERSION_PATCH"}"; + + # Not the whole checkout: third_party/ (the static curl/mbedTLS/ncurses + # sources, the NDK builds), the build directories and the session store are + # gigabytes this build never reads -- curl, ncurses and Qt come from nixpkgs. + src = builtins.filterSource + (path: type: + !(builtins.elem (baseNameOf path) [ + ".agent" + ".cursor" + ".gen" + ".git" + "build-android-arm64-v8a" + "build-apk-android" + "build-win-tui" + "dist" + "result" + "third_party" + ])) + ./.; + + nativeBuildInputs = with pkgs; [ cmake pkg-config qt6.wrapQtAppsHook ]; + # qtwayland so a Wayland session gets a native client, qtsvg for the icon + # theme: Breeze's icons are SVG, and without the image format plugin the + # window falls back to text glyphs. + buildInputs = with pkgs; [ + qt6.qtbase + qt6.qtdeclarative + qt6.qtwayland + qt6.qtsvg + curl + curl.dev + ncurses + ncurses.dev + ]; + + cmakeFlags = [ "-DCST_BUILD_APP=ON" "-DCMAKE_BUILD_TYPE=Release" ]; + + # The "app" component is the window, its launcher entry and the icon; + # app/CMakeLists.txt is the one place that knows where each goes. + installPhase = '' + runHook preInstall + cmake --install . --component app --prefix "$out" + runHook postInstall + ''; + + doCheck = true; + # One test cannot pass here: tests/test_sandbox.c has the engine run a + # sandboxed command and asserts Landlock *denied* the write outside the + # workspace, and Landlock does not work inside the nix build sandbox -- the + # command succeeds and the assertion trips. The other 40 run; the full set + # is `make check` / `ctest` in the dev tree. + checkPhase = '' + runHook preCheck + ctest --output-on-failure -j"$NIX_BUILD_CORES" -E test_sandbox + runHook postCheck + ''; + # The tests write settings, sessions, undo files and a scratch workspace: + # give them a home of their own. + preCheck = '' + export HOME="$TMPDIR/home" + export XDG_CONFIG_HOME="$HOME/.config" XDG_CACHE_HOME="$HOME/.cache" + export XDG_DATA_HOME="$HOME/.local/share" + mkdir -p "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME" "$XDG_DATA_HOME" + ''; + + meta = { + description = + "Local coding agent: Qt Quick front end with the engine in-process"; + homepage = "https://git.whcself.xyz/WHC/CST-agent"; + platforms = pkgs.lib.platforms.linux; + mainProgram = "cst-agent-app"; + }; +}