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";
+ };
+}