feat: rebrand as Hermit — TUI palette, dual glyphs, binary 0.10.0
Ship hermit binary (cst-agent symlink), ~/.config/hermit with legacy fallback, Hermit color theme and mini crab frames, plus SVG→PUA font build pipeline. Co-authored-by: Cursor <cursoragent@cursor.com>
@@ -37,14 +37,14 @@ jobs:
|
|||||||
- name: Build TinyCC
|
- name: Build TinyCC
|
||||||
run: bash scripts/ci_tcc.sh
|
run: bash scripts/ci_tcc.sh
|
||||||
- name: Build
|
- name: Build
|
||||||
run: make CC=tcc AR=ar CFLAGS="-std=c11 -Wall -O2 -g" -j"$(nproc)"
|
run: make CC=tcc AR=ar CFLAGS="-std=c11 -Wall -O2 -g" -j"$(nproc)" hermit
|
||||||
- name: Unit tests
|
- name: Unit tests
|
||||||
run: make CC=tcc AR=ar CFLAGS="-std=c11 -Wall -O2 -g" check
|
run: make CC=tcc AR=ar CFLAGS="-std=c11 -Wall -O2 -g" check
|
||||||
- name: Smoke test
|
- name: Smoke test
|
||||||
run: |
|
run: |
|
||||||
./cst-agent --help
|
./hermit --help
|
||||||
./cst-agent --workspace "$PWD" --list-sessions
|
./hermit --workspace "$PWD" --list-sessions
|
||||||
./cst-agent --demo-ctl --workspace .
|
./hermit --demo-ctl --workspace .
|
||||||
- name: WebUI end-to-end (recorded stream, no API key)
|
- name: WebUI end-to-end (recorded stream, no API key)
|
||||||
run: sh scripts/e2e_webui.sh
|
run: sh scripts/e2e_webui.sh
|
||||||
|
|
||||||
@@ -103,14 +103,14 @@ jobs:
|
|||||||
run: bash scripts/package_linux.sh ${{ matrix.arch }}
|
run: bash scripts/package_linux.sh ${{ matrix.arch }}
|
||||||
- name: Smoke test
|
- name: Smoke test
|
||||||
run: |
|
run: |
|
||||||
d=$(ls -d dist/cst-agent-*-linux-${{ matrix.arch }})
|
d=$(ls -d dist/hermit-*-linux-${{ matrix.arch }})
|
||||||
file "$d/cst-agent"
|
file "$d/hermit"
|
||||||
run=""
|
run=""
|
||||||
[ "${{ matrix.arch }}" = aarch64 ] && run=qemu-aarch64-static
|
[ "${{ matrix.arch }}" = aarch64 ] && run=qemu-aarch64-static
|
||||||
$run "$d/cst-agent" --version
|
$run "$d/hermit" --version
|
||||||
$run "$d/cst-agent" --help >/dev/null
|
$run "$d/hermit" --help >/dev/null
|
||||||
$run "$d/cst-agent" --workspace "$PWD" --list-sessions
|
$run "$d/hermit" --workspace "$PWD" --list-sessions
|
||||||
cat dist/cst-agent-*-linux-${{ matrix.arch }}.tar.gz.sha256
|
cat dist/hermit-*-linux-${{ matrix.arch }}.tar.gz.sha256
|
||||||
# Release vVERSION (from include/cst_version.h) on Gitea. Prefer repo
|
# Release vVERSION (from include/cst_version.h) on Gitea. Prefer repo
|
||||||
# secret RELEASE_TOKEN (write:repository); fall back to the job token.
|
# secret RELEASE_TOKEN (write:repository); fall back to the job token.
|
||||||
- name: Publish Gitea Release
|
- name: Publish Gitea Release
|
||||||
@@ -121,7 +121,7 @@ jobs:
|
|||||||
CST_GITEA: ${{ github.server_url }}
|
CST_GITEA: ${{ github.server_url }}
|
||||||
CST_REPO: ${{ github.repository }}
|
CST_REPO: ${{ github.repository }}
|
||||||
GITHUB_REF: ${{ github.ref }}
|
GITHUB_REF: ${{ github.ref }}
|
||||||
run: bash scripts/ci_publish.sh dist/cst-agent-*-linux-${{ matrix.arch }}.tar.gz
|
run: bash scripts/ci_publish.sh dist/hermit-*-linux-${{ matrix.arch }}.tar.gz dist/cst-agent-*-linux-${{ matrix.arch }}.tar.gz
|
||||||
|
|
||||||
# Release: mingw-w64 UCRT gcc (Debian trixie) with -Werror.
|
# Release: mingw-w64 UCRT gcc (Debian trixie) with -Werror.
|
||||||
# dist/cst-agent-VERSION-windows-x86_64.zip and -setup.exe (NSIS)
|
# dist/cst-agent-VERSION-windows-x86_64.zip and -setup.exe (NSIS)
|
||||||
|
|||||||
@@ -58,3 +58,6 @@ libcstcore.a
|
|||||||
|
|
||||||
# nix-build
|
# nix-build
|
||||||
result
|
result
|
||||||
|
hermit
|
||||||
|
.peer_probe.out
|
||||||
|
reports/
|
||||||
|
|||||||
@@ -152,6 +152,7 @@ set(CST_CORE_SOURCES
|
|||||||
src/models.c
|
src/models.c
|
||||||
src/model_registry.c
|
src/model_registry.c
|
||||||
src/metrics.c
|
src/metrics.c
|
||||||
|
src/hermit_glyph.c
|
||||||
src/net.c
|
src/net.c
|
||||||
src/http_srv.c
|
src/http_srv.c
|
||||||
src/web_md.c
|
src/web_md.c
|
||||||
@@ -257,6 +258,7 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_executable(cst-agent ${CST_SOURCES})
|
add_executable(cst-agent ${CST_SOURCES})
|
||||||
|
set_target_properties(cst-agent PROPERTIES OUTPUT_NAME hermit)
|
||||||
add_dependencies(cst-agent sashidown)
|
add_dependencies(cst-agent sashidown)
|
||||||
cst_win_exe(cst-agent)
|
cst_win_exe(cst-agent)
|
||||||
|
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ CORE_SRC = \
|
|||||||
src/mm_tool.c \
|
src/mm_tool.c \
|
||||||
src/models.c src/model_registry.c \
|
src/models.c src/model_registry.c \
|
||||||
src/metrics.c \
|
src/metrics.c \
|
||||||
|
src/hermit_glyph.c \
|
||||||
$(WEB_SRC)
|
$(WEB_SRC)
|
||||||
|
|
||||||
SRC = $(CORE_SRC) src/textsel.c src/tui.c src/main.c
|
SRC = $(CORE_SRC) src/textsel.c src/tui.c src/main.c
|
||||||
@@ -206,18 +207,23 @@ TEST_LIBS = -L$(SD_DIR) -lsashidown $(CURL_LIBS) $(RPATH_LIBS) $(TEST_SYS_LIBS)
|
|||||||
TEST_OS_SRC = src/os_compat.c src/os_win.c src/util.c
|
TEST_OS_SRC = src/os_compat.c src/os_win.c src/util.c
|
||||||
|
|
||||||
.PHONY: all clean check demo sashidown-lib cmake-gcc cmake-clang asan install \
|
.PHONY: all clean check demo sashidown-lib cmake-gcc cmake-clang asan install \
|
||||||
uninstall install-service help
|
uninstall install-service help hermit-font cst-agent
|
||||||
|
|
||||||
all: sashidown-lib cst-agent $(CST_CORE_LIB)
|
all: sashidown-lib hermit $(CST_CORE_LIB)
|
||||||
|
|
||||||
|
# Compat alias: many scripts still say `make cst-agent`.
|
||||||
|
cst-agent: hermit
|
||||||
|
|
||||||
help:
|
help:
|
||||||
@echo "CST-Agent build targets (prefer: make -j\$$(nproc))"
|
@echo "Hermit build targets (prefer: make -j\$$(nproc))"
|
||||||
@echo " all / cst-agent incremental build (ccache auto if installed)"
|
@echo " all / hermit incremental build (ccache auto if installed)"
|
||||||
@echo " check build + run unit tests"
|
@echo " check build + run unit tests"
|
||||||
@echo " asan AddressSanitizer rebuild + check"
|
@echo " asan AddressSanitizer rebuild + check"
|
||||||
@echo " install PREFIX=$(PREFIX) (default /usr/local)"
|
@echo " hermit-font optional SVG→color font (needs rsvg + fonttools)"
|
||||||
|
@echo " install PREFIX=$(PREFIX); hermit + cst-agent symlink"
|
||||||
@echo " install-service systemd --user parliament unit"
|
@echo " install-service systemd --user parliament unit"
|
||||||
@echo "Env: USE_CCACHE=0|1|auto STATIC=1 CST_WEBUI=0 CC=tcc"
|
@echo "Env: USE_CCACHE=0|1|auto STATIC=1 CST_WEBUI=0 CC=tcc"
|
||||||
|
@echo " HERMIT_ICONS=auto|mini|font HERMIT_*/CST_* env"
|
||||||
@echo "Release: scripts/package_linux.sh ARCH | scripts/ci_windows_cross.sh"
|
@echo "Release: scripts/package_linux.sh ARCH | scripts/ci_windows_cross.sh"
|
||||||
@echo "Fast install: scripts/bootstrap_linux.sh --release"
|
@echo "Fast install: scripts/bootstrap_linux.sh --release"
|
||||||
|
|
||||||
@@ -238,8 +244,13 @@ cmake-clang:
|
|||||||
sashidown-lib:
|
sashidown-lib:
|
||||||
$(MAKE) -C $(SD_DIR) libsashidown.a
|
$(MAKE) -C $(SD_DIR) libsashidown.a
|
||||||
|
|
||||||
cst-agent: $(OBJ) sashidown-lib
|
hermit: $(OBJ) sashidown-lib
|
||||||
$(CC) $(CFLAGS) -o $@ $(OBJ) $(LDFLAGS) $(LDLIBS)
|
$(CC) $(CFLAGS) -o $@ $(OBJ) $(LDFLAGS) $(LDLIBS)
|
||||||
|
@ln -sfr $@ cst-agent 2>/dev/null || ln -sf hermit cst-agent
|
||||||
|
|
||||||
|
hermit-font:
|
||||||
|
@python3 scripts/build_hermit_font.py || \
|
||||||
|
echo "hermit-font: skipped (install rsvg-convert + fonttools to build)"
|
||||||
|
|
||||||
# Built-in model data as a C byte list (included by model_registry.c).
|
# Built-in model data as a C byte list (included by model_registry.c).
|
||||||
.gen/models_builtin.inc: src/models_builtin.json
|
.gen/models_builtin.inc: src/models_builtin.json
|
||||||
@@ -303,32 +314,38 @@ check: sashidown-lib $(TESTS)
|
|||||||
asan:
|
asan:
|
||||||
$(MAKE) clean
|
$(MAKE) clean
|
||||||
$(MAKE) CFLAGS="-std=c11 -Wall -Wextra -O1 -g -fsanitize=address,undefined -fno-sanitize-recover=undefined -fno-omit-frame-pointer" \
|
$(MAKE) CFLAGS="-std=c11 -Wall -Wextra -O1 -g -fsanitize=address,undefined -fno-sanitize-recover=undefined -fno-omit-frame-pointer" \
|
||||||
sashidown-lib cst-agent check
|
sashidown-lib hermit check
|
||||||
|
|
||||||
demo: cst-agent
|
demo: hermit
|
||||||
./cst-agent --demo-ctl --workspace .
|
./hermit --demo-ctl --workspace .
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(OBJ) $(DEPFILES) cst-agent $(CST_CORE_LIB) $(TESTS) \
|
rm -f $(OBJ) $(DEPFILES) hermit cst-agent $(CST_CORE_LIB) $(TESTS) \
|
||||||
.gen/models_builtin.inc .gen/web_assets.inc
|
.gen/models_builtin.inc .gen/web_assets.inc
|
||||||
|
|
||||||
install: cst-agent
|
install: hermit
|
||||||
install -d "$(DESTDIR)$(PREFIX)/bin"
|
install -d "$(DESTDIR)$(PREFIX)/bin"
|
||||||
install -m 0755 cst-agent "$(DESTDIR)$(PREFIX)/bin/cst-agent"
|
install -m 0755 hermit "$(DESTDIR)$(PREFIX)/bin/hermit"
|
||||||
|
ln -sfr "$(DESTDIR)$(PREFIX)/bin/hermit" "$(DESTDIR)$(PREFIX)/bin/cst-agent" 2>/dev/null || \
|
||||||
|
ln -sf hermit "$(DESTDIR)$(PREFIX)/bin/cst-agent"
|
||||||
|
@if [ -f res/hermit-icons.ttf ]; then \
|
||||||
|
install -d "$(DESTDIR)$(PREFIX)/share/hermit"; \
|
||||||
|
install -m 0644 res/hermit-icons.ttf "$(DESTDIR)$(PREFIX)/share/hermit/"; \
|
||||||
|
fi
|
||||||
|
|
||||||
# Optional systemd --user unit for the parliament seat service.
|
# Optional systemd --user unit for the parliament seat service.
|
||||||
install-service: install
|
install-service: install
|
||||||
install -d "$(DESTDIR)$(HOME)/.config/systemd/user"
|
install -d "$(DESTDIR)$(HOME)/.config/systemd/user"
|
||||||
sed -e 's|@BIN@|$(PREFIX)/bin/cst-agent|g' \
|
sed -e 's|@BIN@|$(PREFIX)/bin/hermit|g' \
|
||||||
scripts/cst-agent-parliament.service.in \
|
scripts/cst-agent-parliament.service.in \
|
||||||
> "$(DESTDIR)$(HOME)/.config/systemd/user/cst-agent-parliament.service"
|
> "$(DESTDIR)$(HOME)/.config/systemd/user/cst-agent-parliament.service"
|
||||||
@echo "Installed user unit. Enable with:"
|
@echo "Installed user unit. Enable with:"
|
||||||
@echo " systemctl --user daemon-reload"
|
@echo " systemctl --user daemon-reload"
|
||||||
@echo " systemctl --user enable --now cst-agent-parliament.service"
|
@echo " systemctl --user enable --now cst-agent-parliament.service"
|
||||||
@echo "(set CST_REALM_PASSWORD / CST_INSTANCE_ID in the drop-in or environment)"
|
@echo "(set HERMIT_/CST_ REALM_PASSWORD / INSTANCE_ID in the drop-in or environment)"
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
rm -f "$(DESTDIR)$(PREFIX)/bin/cst-agent"
|
rm -f "$(DESTDIR)$(PREFIX)/bin/hermit" "$(DESTDIR)$(PREFIX)/bin/cst-agent"
|
||||||
rm -f "$(HOME)/.config/systemd/user/cst-agent-parliament.service"
|
rm -f "$(HOME)/.config/systemd/user/cst-agent-parliament.service"
|
||||||
|
|
||||||
-include $(DEPFILES)
|
-include $(DEPFILES)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# CST-Agent
|
# Hermit
|
||||||
|
|
||||||
本地 coding agent:终端 TUI、可选 WebUI / 桌面 GUI,在工作区里读改文件、跑命令、管会话。
|
本地 coding agent(原 CST-Agent):终端 TUI、可选 WebUI / 桌面 GUI,在工作区里读改文件、跑命令、管会话。
|
||||||
C11 实现,OpenAI 兼容网关(DeepSeek / Kimi / MiniMax / OpenAI 等)。
|
C11 实现,OpenAI 兼容网关(DeepSeek / Kimi / MiniMax / OpenAI 等)。
|
||||||
|
|
||||||
**适合**:有监督的本地改动与仓库探索(权限确认、diff、`/undo`、沙箱)。
|
**适合**:有监督的本地改动与仓库探索(权限确认、diff、`/undo`、沙箱)。
|
||||||
@@ -8,188 +8,80 @@ C11 实现,OpenAI 兼容网关(DeepSeek / Kimi / MiniMax / OpenAI 等)。
|
|||||||
|
|
||||||
文档索引:[docs/README.md](docs/README.md)。
|
文档索引:[docs/README.md](docs/README.md)。
|
||||||
|
|
||||||
|
兼容:安装后提供 `cst-agent` → `hermit` 软链;环境变量 `HERMIT_*` 优先,仍接受 `CST_*`;配置目录优先 `~/.config/hermit`,若无则回退 `~/.config/cst-agent`。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 安装
|
## 安装
|
||||||
|
|
||||||
### Linux / WSL / macOS(推荐)
|
### Linux / WSL / macOS
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 预编译包(快;失败则自动回退源码编译)
|
# 预编译包(快;失败则回退源码)
|
||||||
curl -fsSL https://git.whcself.xyz/WHC/CST-agent/raw/branch/main/scripts/bootstrap_linux.sh | bash -s -- --release
|
curl -fsSL https://git.whcself.xyz/WHC/CST-agent/raw/branch/main/scripts/bootstrap_linux.sh | bash -s -- --release
|
||||||
|
|
||||||
# 或始终从源码增量编译安装到 ~/.local/bin
|
# 源码增量编译 → ~/.local/bin/hermit(+ cst-agent 软链)
|
||||||
curl -fsSL https://git.whcself.xyz/WHC/CST-agent/raw/branch/main/scripts/bootstrap_linux.sh | bash
|
curl -fsSL https://git.whcself.xyz/WHC/CST-agent/raw/branch/main/scripts/bootstrap_linux.sh | bash
|
||||||
```
|
```
|
||||||
|
|
||||||
已有仓库目录:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./scripts/bootstrap_linux.sh . # 源码
|
./scripts/bootstrap_linux.sh .
|
||||||
./scripts/bootstrap_linux.sh . --release # 只装 Release 二进制
|
hermit --version # hermit 0.10.0
|
||||||
cst-agent-update # 之后升级
|
cst-agent-update # 升级助手仍可用
|
||||||
cst-agent --version
|
|
||||||
```
|
```
|
||||||
|
|
||||||
[Release](https://git.whcself.xyz/WHC/CST-agent/releases) 也提供静态 musl 包
|
[Release](https://git.whcself.xyz/WHC/CST-agent/releases):`hermit-VERSION-linux-{x86_64,x86,aarch64}.tar.gz`。
|
||||||
`cst-agent-VERSION-linux-{x86_64,x86,aarch64}.tar.gz`(旁有 `.sha256`),解压后把 `cst-agent` 放进 `PATH` 即可。
|
|
||||||
|
|
||||||
### Windows 10 1903+
|
### Windows
|
||||||
|
|
||||||
```powershell
|
见 [docs/README-windows.md](docs/README-windows.md)(产物名逐步改为 `hermit-…`;旧 `cst-agent-…` 包过渡期可能仍有)。
|
||||||
irm https://git.whcself.xyz/WHC/CST-agent/raw/branch/main/scripts/install_windows.ps1 | iex
|
|
||||||
```
|
|
||||||
|
|
||||||
或下载 `cst-agent-VERSION-windows-x86_64-setup.exe` / 便携 zip。详见 [docs/README-windows.md](docs/README-windows.md)。
|
|
||||||
|
|
||||||
### Termux
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -fsSL https://git.whcself.xyz/WHC/CST-agent/raw/branch/main/scripts/bootstrap_termux.sh | bash
|
|
||||||
```
|
|
||||||
|
|
||||||
### 第一次用
|
### 第一次用
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ~/my-project && cst-agent
|
cd ~/my-project && hermit
|
||||||
```
|
```
|
||||||
|
|
||||||
首次选择 provider 并输入 API key(遮罩输入,按 provider 存到配置目录 `credentials`,权限 0600)。
|
|
||||||
之后用 `/provider`、`/model`、`/effort`、`/key` 切换;选择会记在 `settings.json`。
|
|
||||||
|
|
||||||
完整环境变量:[docs/env_openai.md](docs/env_openai.md)。
|
|
||||||
多设备议会:[docs/parliament.md](docs/parliament.md)。
|
|
||||||
|
|
||||||
| 平台 | 脚本 |
|
|
||||||
|---|---|
|
|
||||||
| Linux / macOS | [`scripts/bootstrap_linux.sh`](scripts/bootstrap_linux.sh) |
|
|
||||||
| Termux | [`scripts/bootstrap_termux.sh`](scripts/bootstrap_termux.sh) |
|
|
||||||
| Windows 安装 | [`scripts/install_windows.ps1`](scripts/install_windows.ps1) |
|
|
||||||
| Linux 静态包 | [`scripts/package_linux.sh`](scripts/package_linux.sh) `x86_64\|x86\|aarch64` |
|
|
||||||
| Windows 交叉构建 | [`scripts/bootstrap_windows.sh`](scripts/bootstrap_windows.sh) |
|
|
||||||
| Windows 原生(MSYS2) | [`scripts/bootstrap_msys2.sh`](scripts/bootstrap_msys2.sh) |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 构建
|
## 构建
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make -j$(nproc) # 增量;有 ccache 会自动用;make help 看目标
|
make -j$(nproc) # 产出 hermit(并链 cst-agent)
|
||||||
make -j$(nproc) check # 单元测试
|
make -j$(nproc) check
|
||||||
make asan # ASan + UBSan
|
make hermit-font # 可选:SVG→PUA 字体(需 rsvg/fonttools)
|
||||||
make CC=tcc AR=ar check # TinyCC 快校验(CI 同款)
|
|
||||||
make install PREFIX=$HOME/.local
|
make install PREFIX=$HOME/.local
|
||||||
make install-service PREFIX=$HOME/.local # 可选:systemd --user 议会单元
|
|
||||||
```
|
```
|
||||||
|
|
||||||
NixOS:`nix-shell --run 'make -j$(nproc)'`。macOS:`brew install curl ncurses pkg-config` 后直接 `make`。
|
TUI 图标:`HERMIT_ICONS=auto|mini|font`(默认极简字符帧;`font` 需安装 `share/hermit/hermit-icons.ttf`)。
|
||||||
平台细节:[docs/compat.md](docs/compat.md)。
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# CMake(多编译器 / 无 TUI)
|
|
||||||
cmake -B build -DCMAKE_C_COMPILER=gcc -DCST_BUILD_TUI=ON
|
|
||||||
cmake --build build -j && ctest --test-dir build --output-on-failure
|
|
||||||
```
|
|
||||||
|
|
||||||
可选 Qt 桌面前端:[app/README.md](app/README.md)。
|
|
||||||
Android APK:[docs/android.md](docs/android.md)。
|
|
||||||
|
|
||||||
### CI 与发布
|
|
||||||
|
|
||||||
推送 `main` / `v*` 时 [`.gitea/workflows/ci.yml`](.gitea/workflows/ci.yml) 会:TinyCC `check` → 打 Linux/Windows 发行包 → `scripts/ci_publish.sh` 上传 Gitea Release。
|
|
||||||
共用逻辑:[`scripts/lib_release.sh`](scripts/lib_release.sh)。本地上传:`GITEA_TOKEN=... scripts/release.sh dist/cst-agent-*`。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 用法速览
|
## 用法速览
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cst-agent # TUI,工作区=当前目录
|
hermit # TUI
|
||||||
cst-agent --web # 浏览器 http://127.0.0.1:8787/
|
hermit --web # http://127.0.0.1:8787/
|
||||||
cst-agent --headless --prompt '…' # 无人值守(默认自动批准)
|
hermit --headless --prompt '…'
|
||||||
cst-agent --ask-key --provider deepseek
|
|
||||||
```
|
```
|
||||||
|
|
||||||
| 参数 | 作用 |
|
首页品牌 **HERMIT** + 寄居蟹色块动效;色板:brand `#277F94`、crab `#F68042`、eye `#9957AD`。
|
||||||
|---|---|
|
|
||||||
| `-c` / `-N` / `-R ID` / `-L` | 继续上次 / 新会话 / 恢复 / 列表 |
|
|
||||||
| `-A ask\|auto\|yolo` / `-y` | 审批模式 |
|
|
||||||
| `-T default\|minimal` | 工具集(`minimal` = 读写 shell 四件套) |
|
|
||||||
| `-M coding\|general` | 工作模式 |
|
|
||||||
| `-P` / `-m` / `-e` | provider / 模型 / 推理强度 |
|
|
||||||
| `-w DIR` | 工作区 |
|
|
||||||
|
|
||||||
会话目录:`<workspace>/.agent/sessions/<uuid>/`(自带 `.gitignore`)。WebUI 协议:[docs/webui.md](docs/webui.md)。
|
更多:Provider / 工具 / 议会 / WebUI 见 [docs/](docs/)。
|
||||||
|
|
||||||
### Provider 默认
|
|
||||||
|
|
||||||
| provider | API Base | 默认模型 | Key |
|
|
||||||
|---|---|---|---|
|
|
||||||
| `minimax`(默认) | `https://api.minimax.cn/v1` | `MiniMax-M2.7` | `MINIMAX_API_KEY` |
|
|
||||||
| `deepseek` | `https://api.deepseek.com/v1` | `deepseek-flash` | `DEEPSEEK_API_KEY` |
|
|
||||||
| `kimi` | `https://api.moonshot.ai/v1` | `kimi-k3` | `MOONSHOT_API_KEY` |
|
|
||||||
| `kimi-code` | `https://api.kimi.ai/coding/v1` | `k3` | `KIMI_API_KEY` |
|
|
||||||
| `openai` | `https://api.openai.com/v1` | `gpt-4.1-mini` | `OPENAI_API_KEY` |
|
|
||||||
| `teamorouter` | `https://api.teamorouter.com/v1` | `/models` 首个 | `TEAMOROUTER_API_KEY` |
|
|
||||||
|
|
||||||
优先级:CLI → 已保存 credentials → 环境变量 → TUI 询问。详见 [docs/env_openai.md](docs/env_openai.md)。
|
|
||||||
|
|
||||||
### 内置能力(摘要)
|
|
||||||
|
|
||||||
- **文件**:`read_file` / `list_dir` / `glob` / `grep` / `edit_file` / `apply_patch` / `write_file` / `delete_file` / `move_file`
|
|
||||||
- **命令**:`run_shell`(沙箱 + 可中断;超时 `CST_SHELL_TIMEOUT`)
|
|
||||||
- **网络**:`web_fetch`;Kimi/DeepSeek 另有官方 `web_search`
|
|
||||||
- **任务**:`todo_write`、`ask_user`、`task` 子 agent、后台 `job_*`(完成会推 TUI / 下一回合笔记)
|
|
||||||
- **扩展**:Skills、插件、MCP([docs/plugins.md](docs/plugins.md));worker 编排([docs/workers.md](docs/workers.md))
|
|
||||||
|
|
||||||
同一步里连续只读调用会自动并行(默认最多 8,`CST_PAR_MAX` 可调)。截断结果带 `[truncated … evidence=ID]`,用 `evidence_read` 续读。
|
|
||||||
|
|
||||||
### 审批与沙箱
|
|
||||||
|
|
||||||
Shift+Tab / `/approval` 在 `ask` → `auto` → `yolo` 间切换。`auto` 下 Linux 优先 Landlock(否则 seccomp),macOS `sandbox_init`,Windows Low IL;详见源码与 [docs/compat.md](docs/compat.md)。
|
|
||||||
写文件前自动快照,`/undo` 可回滚;工作区 `AGENTS.md` 会注入系统提示。
|
|
||||||
|
|
||||||
### TUI 常用
|
|
||||||
|
|
||||||
| 按键 / 命令 | 说明 |
|
|
||||||
|---|---|
|
|
||||||
| Enter / Esc | 发送(busy 时入队)/ 取消当前回合 |
|
|
||||||
| Tab / Shift+Tab | 补全或 Build↔Plan / 审批模式 |
|
|
||||||
| Ctrl+P / Ctrl+T | 命令面板 / 任务面板 |
|
|
||||||
| Ctrl+X 再按字母 | Leader 快捷键(`/keys` 或 F1 查看) |
|
|
||||||
| `/provider` `/model` `/effort` `/key` | 切换后端 |
|
|
||||||
| `/realm` `/motion` | 议会登录提示、Seat Deck 动效 |
|
|
||||||
| `/web` `/quit` | 起 WebUI / 退出 |
|
|
||||||
|
|
||||||
完整快捷键与斜杠命令仍以 TUI 内 `/help`、`/keys` 为准。
|
|
||||||
|
|
||||||
### 工具协议(可选)
|
|
||||||
|
|
||||||
默认 `native-eos`(OpenAI `tools[]`,流结束后执行)。也可 `native-inc`、文本帧 `ctl` / `ctl-heredoc`、`xml`(`CST_PROTOCOL` / `/protocol`)。日常使用保持默认即可。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 平台
|
## CI 与发布
|
||||||
|
|
||||||
| OS | Arch | TUI | 备注 |
|
`.gitea/workflows/ci.yml`:TinyCC check → 打 Linux/Windows 包 → `scripts/ci_publish.sh`。
|
||||||
|---|---|---|---|
|
共用逻辑:[scripts/lib_release.sh](scripts/lib_release.sh)。
|
||||||
| Linux glibc/musl | x86_64 / x86 / aarch64 | ncursesw | 静态 Release 为 musl |
|
|
||||||
| WSL2 | x86_64 | ncursesw | 工作区放在 Linux 文件系统 |
|
|
||||||
| macOS | arm64 / x86_64 | Homebrew ncurses | |
|
|
||||||
| Termux | aarch64 等 | Termux ncurses | |
|
|
||||||
| Windows 10 1903+ | x86_64 | 捆绑 ncurses | shell 为 busybox-w32 |
|
|
||||||
|
|
||||||
---
|
## 开发者
|
||||||
|
|
||||||
## 开发者备忘
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make check
|
make check
|
||||||
scripts/prompt_smoke.sh # 系统提示契约(无 API)
|
scripts/prompt_smoke.sh
|
||||||
scripts/e2e_webui.sh # WebUI 录制流(无 API)
|
scripts/e2e_webui.sh
|
||||||
scripts/e2e_fixture.sh mini_calc --provider deepseek # 可选:带 key 的夹具
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`fixtures/` 下是可重复的本地任务夹具(`TASK.md` + `check.sh`),用于手工或脚本验收,不是产品依赖。
|
`icons/agent-motion/` 为 SVG 资产;TUI 默认用字体序列极简轨,`make hermit-font` 生成 PUA 字体轨。
|
||||||
|
|
||||||
许可证与第三方:`vendor/sashidown`、`vendor/cjson`(MIT)、`vendor/monocypher` 随仓库提供。
|
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
# 文档索引
|
# Hermit 文档索引
|
||||||
|
|
||||||
面向日常使用的说明。二进制名仍是 `cst-agent`,配置目录默认 `~/.config/cst-agent/`。
|
二进制:`hermit`(兼容软链 `cst-agent`)。配置目录:`~/.config/hermit`(若无则回退 `~/.config/cst-agent`)。
|
||||||
|
|
||||||
| 文档 | 内容 |
|
| 文档 | 内容 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| [env_openai.md](env_openai.md) | Provider、模型、API Key、环境变量 |
|
| [env_openai.md](env_openai.md) | Provider、模型、API Key、环境变量(`HERMIT_*` / `CST_*`) |
|
||||||
| [parliament.md](parliament.md) | 多设备议会(Realm / 席位 / 同步) |
|
| [parliament.md](parliament.md) | 多设备议会 |
|
||||||
| [workers.md](workers.md) | 后台任务、worker、job 完成报告 |
|
| [workers.md](workers.md) | 后台任务、worker、job 完成报告 |
|
||||||
| [webui.md](webui.md) | 内置 HTTP + SSE WebUI |
|
| [webui.md](webui.md) | 内置 HTTP + SSE WebUI |
|
||||||
| [plugins.md](plugins.md) | Skills、插件、MCP |
|
| [plugins.md](plugins.md) | Skills、插件、MCP |
|
||||||
| [compat.md](compat.md) | 平台兼容与验收清单 |
|
| [compat.md](compat.md) | 平台兼容 |
|
||||||
| [README-windows.md](README-windows.md) | Windows 安装、沙箱、构建 |
|
| [README-windows.md](README-windows.md) | Windows |
|
||||||
| [android.md](android.md) | Android / APK |
|
| [android.md](android.md) | Android / APK |
|
||||||
| [windows_selftest.md](windows_selftest.md) | Windows 自测步骤 |
|
|
||||||
| [archive/](archive/) | 历史设计笔记(非产品文档) |
|
| [archive/](archive/) | 历史设计笔记(非产品文档) |
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Hermit / 寄居蟹 · 扁平 SVG 动图包
|
||||||
|
|
||||||
|
设计原则:遵循原始草图。纯色块、橙色方形身体、紫色眼睛、短脚;无渐变、无阴影、无高光。蓝色终端方块就是寄居蟹的壳。
|
||||||
|
|
||||||
|
6 个状态 × 2 个版本,共 12 个 SVG:
|
||||||
|
- idle:等待输入
|
||||||
|
- question:请求问题
|
||||||
|
- thinking:思考,黄色小钳子托腮
|
||||||
|
- coding:写码,>_ 眼睛
|
||||||
|
- success:完成
|
||||||
|
- error:遇到问题
|
||||||
|
|
||||||
|
icons/{状态}-shell.svg 是大的带壳图标;icons/{状态}.svg 是小的无壳图标。透明背景、CSS 循环动画,支持系统减少动态效果。
|
||||||
|
|
||||||
|
直接打开 index.html 查看,支持深浅背景、暂停、速度调整、自动轮播以及两个版本的 SVG 下载。无联网依赖。
|
||||||
|
|
||||||
|
接入示例:<img src="icons/idle.svg" width="24" alt="等待输入">。大图标建议 64px 及以上,小图标建议 24–32px;16px 时状态细节较弱。使用内联 SVG 时可通过 --speed 设置速度。
|
||||||
|
|
||||||
|
普通字符终端不直接渲染 SVG;图标适用于支持图形的终端界面、Webview 或 CLI 伴随应用。
|
||||||
|
|
||||||
|
更新:橘杏色身体、莓紫眼睛、低饱和蓝绿终端壳。预览主角色以 620ms 缓入缓出插值连续改变眼睛路径与身体姿态,支持中途切换;不重建主角色。状态符号淡入淡出。独立 SVG 保留各状态循环动效;跨状态变形逻辑位于 index.html。
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 80" class="coding" role="img" aria-label="Hermit 写码 带壳"><title>Hermit · 写码</title><style>svg{--speed:1}.hermit-body{transform-origin:32px 35px;animation:hermit-idle calc(3.8s / var(--speed)) cubic-bezier(.45,0,.55,1) infinite}.hermit-eyes{transform-box:fill-box;transform-origin:center;animation:hermit-blink calc(5.4s / var(--speed)) ease-in-out infinite}.question .hermit-body{animation-name:hermit-ask}.coding .hermit-eyes{animation:none}.coding .hermit-body{animation-name:hermit-code;animation-duration:calc(1.4s / var(--speed))}.success .hermit-body{animation-name:hermit-joy}.error .hermit-body{animation-name:hermit-care}.hermit-mark{animation:hermit-mark calc(2.8s / var(--speed)) ease-in-out infinite}.thinking .hermit-eyes{animation:hermit-look calc(3.8s / var(--speed)) ease-in-out infinite}@keyframes hermit-idle{0%,100%{transform:translateY(0) rotate(-1deg) scale(1,1)}50%{transform:translateY(-1.2px) rotate(1deg) scale(1.015,.985)}}@keyframes hermit-blink{0%,42%,48%,100%{transform:scaleY(1)}45%{transform:scaleY(.12)}}@keyframes hermit-ask{0%,100%{transform:rotate(-2deg) translateY(0)}45%,65%{transform:rotate(6deg) translateY(-1.3px)}}@keyframes hermit-code{0%,100%{transform:translateY(0) rotate(-1.4deg)}50%{transform:translateY(-1px) rotate(1.4deg)}}@keyframes hermit-joy{0%,100%{transform:translateY(0) scale(1.02,.98)}45%{transform:translateY(-2.8px) rotate(-3deg) scale(.98,1.02)}70%{transform:translateY(-1px) rotate(2deg)}}@keyframes hermit-care{0%,70%,100%{transform:rotate(0)}78%{transform:rotate(-3deg)}88%{transform:rotate(3deg)}}@keyframes hermit-mark{0%,100%{transform:translateY(0) rotate(0)}50%{transform:translateY(-1.7px) rotate(3deg)}}@keyframes hermit-look{0%,25%,100%{transform:translate(0,0)}45%,70%{transform:translate(1.5px,-.6px)}}@media(prefers-reduced-motion:reduce){*{animation:none!important}}</style><rect x="8" y="12" width="68" height="47" rx="3" fill="#277f94"/><path d="m20 25 5 6-5 6m14 1h10" stroke="#a9d9d7" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none"/><g transform="translate(34 23)"><g class="hermit-body"><path d="M23 36v5m18-5v5" stroke="#a4574f" stroke-width="6" stroke-linecap="round"/><rect x="12" y="15" width="36" height="25" rx="4" fill="#f68042"/><g class="hermit-eyes" fill="none" stroke="#9957ad" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="m27 21 4 4-4 4m10-1h5"/></g></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="7 5 52 42" class="coding" role="img" aria-label="Hermit 写码 无壳"><title>Hermit · 写码</title><style>svg{--speed:1}.hermit-body{transform-origin:32px 35px;animation:hermit-idle calc(3.8s / var(--speed)) cubic-bezier(.45,0,.55,1) infinite}.hermit-eyes{transform-box:fill-box;transform-origin:center;animation:hermit-blink calc(5.4s / var(--speed)) ease-in-out infinite}.question .hermit-body{animation-name:hermit-ask}.coding .hermit-eyes{animation:none}.coding .hermit-body{animation-name:hermit-code;animation-duration:calc(1.4s / var(--speed))}.success .hermit-body{animation-name:hermit-joy}.error .hermit-body{animation-name:hermit-care}.hermit-mark{animation:hermit-mark calc(2.8s / var(--speed)) ease-in-out infinite}.thinking .hermit-eyes{animation:hermit-look calc(3.8s / var(--speed)) ease-in-out infinite}@keyframes hermit-idle{0%,100%{transform:translateY(0) rotate(-1deg) scale(1,1)}50%{transform:translateY(-1.2px) rotate(1deg) scale(1.015,.985)}}@keyframes hermit-blink{0%,42%,48%,100%{transform:scaleY(1)}45%{transform:scaleY(.12)}}@keyframes hermit-ask{0%,100%{transform:rotate(-2deg) translateY(0)}45%,65%{transform:rotate(6deg) translateY(-1.3px)}}@keyframes hermit-code{0%,100%{transform:translateY(0) rotate(-1.4deg)}50%{transform:translateY(-1px) rotate(1.4deg)}}@keyframes hermit-joy{0%,100%{transform:translateY(0) scale(1.02,.98)}45%{transform:translateY(-2.8px) rotate(-3deg) scale(.98,1.02)}70%{transform:translateY(-1px) rotate(2deg)}}@keyframes hermit-care{0%,70%,100%{transform:rotate(0)}78%{transform:rotate(-3deg)}88%{transform:rotate(3deg)}}@keyframes hermit-mark{0%,100%{transform:translateY(0) rotate(0)}50%{transform:translateY(-1.7px) rotate(3deg)}}@keyframes hermit-look{0%,25%,100%{transform:translate(0,0)}45%,70%{transform:translate(1.5px,-.6px)}}@media(prefers-reduced-motion:reduce){*{animation:none!important}}</style><g class="hermit-body"><path d="M23 36v5m18-5v5" stroke="#a4574f" stroke-width="6" stroke-linecap="round"/><rect x="12" y="15" width="36" height="25" rx="4" fill="#f68042"/><g class="hermit-eyes" fill="none" stroke="#9957ad" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="m27 21 4 4-4 4m10-1h5"/></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 80" class="error" role="img" aria-label="Hermit 遇到问题 带壳"><title>Hermit · 遇到问题</title><style>svg{--speed:1}.hermit-body{transform-origin:32px 35px;animation:hermit-idle calc(3.8s / var(--speed)) cubic-bezier(.45,0,.55,1) infinite}.hermit-eyes{transform-box:fill-box;transform-origin:center;animation:hermit-blink calc(5.4s / var(--speed)) ease-in-out infinite}.question .hermit-body{animation-name:hermit-ask}.coding .hermit-eyes{animation:none}.coding .hermit-body{animation-name:hermit-code;animation-duration:calc(1.4s / var(--speed))}.success .hermit-body{animation-name:hermit-joy}.error .hermit-body{animation-name:hermit-care}.hermit-mark{animation:hermit-mark calc(2.8s / var(--speed)) ease-in-out infinite}.thinking .hermit-eyes{animation:hermit-look calc(3.8s / var(--speed)) ease-in-out infinite}@keyframes hermit-idle{0%,100%{transform:translateY(0) rotate(-1deg) scale(1,1)}50%{transform:translateY(-1.2px) rotate(1deg) scale(1.015,.985)}}@keyframes hermit-blink{0%,42%,48%,100%{transform:scaleY(1)}45%{transform:scaleY(.12)}}@keyframes hermit-ask{0%,100%{transform:rotate(-2deg) translateY(0)}45%,65%{transform:rotate(6deg) translateY(-1.3px)}}@keyframes hermit-code{0%,100%{transform:translateY(0) rotate(-1.4deg)}50%{transform:translateY(-1px) rotate(1.4deg)}}@keyframes hermit-joy{0%,100%{transform:translateY(0) scale(1.02,.98)}45%{transform:translateY(-2.8px) rotate(-3deg) scale(.98,1.02)}70%{transform:translateY(-1px) rotate(2deg)}}@keyframes hermit-care{0%,70%,100%{transform:rotate(0)}78%{transform:rotate(-3deg)}88%{transform:rotate(3deg)}}@keyframes hermit-mark{0%,100%{transform:translateY(0) rotate(0)}50%{transform:translateY(-1.7px) rotate(3deg)}}@keyframes hermit-look{0%,25%,100%{transform:translate(0,0)}45%,70%{transform:translate(1.5px,-.6px)}}@media(prefers-reduced-motion:reduce){*{animation:none!important}}</style><rect x="8" y="12" width="68" height="47" rx="3" fill="#277f94"/><path d="m20 25 5 6-5 6m14 1h10" stroke="#a9d9d7" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none"/><g transform="translate(34 23)"><g class="hermit-body"><path d="M23 36v5m18-5v5" stroke="#a4574f" stroke-width="6" stroke-linecap="round"/><rect x="12" y="15" width="36" height="25" rx="4" fill="#f68042"/><g class="hermit-eyes" fill="none" stroke="#9957ad" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="m27 22 5 2m6 0 5-2"/></g><path class="hermit-mark" d="M52 12v6m0 5v.1" stroke="#9957ad" stroke-width="3" stroke-linecap="round"/></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="7 5 52 42" class="error" role="img" aria-label="Hermit 遇到问题 无壳"><title>Hermit · 遇到问题</title><style>svg{--speed:1}.hermit-body{transform-origin:32px 35px;animation:hermit-idle calc(3.8s / var(--speed)) cubic-bezier(.45,0,.55,1) infinite}.hermit-eyes{transform-box:fill-box;transform-origin:center;animation:hermit-blink calc(5.4s / var(--speed)) ease-in-out infinite}.question .hermit-body{animation-name:hermit-ask}.coding .hermit-eyes{animation:none}.coding .hermit-body{animation-name:hermit-code;animation-duration:calc(1.4s / var(--speed))}.success .hermit-body{animation-name:hermit-joy}.error .hermit-body{animation-name:hermit-care}.hermit-mark{animation:hermit-mark calc(2.8s / var(--speed)) ease-in-out infinite}.thinking .hermit-eyes{animation:hermit-look calc(3.8s / var(--speed)) ease-in-out infinite}@keyframes hermit-idle{0%,100%{transform:translateY(0) rotate(-1deg) scale(1,1)}50%{transform:translateY(-1.2px) rotate(1deg) scale(1.015,.985)}}@keyframes hermit-blink{0%,42%,48%,100%{transform:scaleY(1)}45%{transform:scaleY(.12)}}@keyframes hermit-ask{0%,100%{transform:rotate(-2deg) translateY(0)}45%,65%{transform:rotate(6deg) translateY(-1.3px)}}@keyframes hermit-code{0%,100%{transform:translateY(0) rotate(-1.4deg)}50%{transform:translateY(-1px) rotate(1.4deg)}}@keyframes hermit-joy{0%,100%{transform:translateY(0) scale(1.02,.98)}45%{transform:translateY(-2.8px) rotate(-3deg) scale(.98,1.02)}70%{transform:translateY(-1px) rotate(2deg)}}@keyframes hermit-care{0%,70%,100%{transform:rotate(0)}78%{transform:rotate(-3deg)}88%{transform:rotate(3deg)}}@keyframes hermit-mark{0%,100%{transform:translateY(0) rotate(0)}50%{transform:translateY(-1.7px) rotate(3deg)}}@keyframes hermit-look{0%,25%,100%{transform:translate(0,0)}45%,70%{transform:translate(1.5px,-.6px)}}@media(prefers-reduced-motion:reduce){*{animation:none!important}}</style><g class="hermit-body"><path d="M23 36v5m18-5v5" stroke="#a4574f" stroke-width="6" stroke-linecap="round"/><rect x="12" y="15" width="36" height="25" rx="4" fill="#f68042"/><g class="hermit-eyes" fill="none" stroke="#9957ad" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="m27 22 5 2m6 0 5-2"/></g><path class="hermit-mark" d="M52 12v6m0 5v.1" stroke="#9957ad" stroke-width="3" stroke-linecap="round"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 80" class="idle" role="img" aria-label="Hermit 等待输入 带壳"><title>Hermit · 等待输入</title><style>svg{--speed:1}.hermit-body{transform-origin:32px 35px;animation:hermit-idle calc(3.8s / var(--speed)) cubic-bezier(.45,0,.55,1) infinite}.hermit-eyes{transform-box:fill-box;transform-origin:center;animation:hermit-blink calc(5.4s / var(--speed)) ease-in-out infinite}.question .hermit-body{animation-name:hermit-ask}.coding .hermit-eyes{animation:none}.coding .hermit-body{animation-name:hermit-code;animation-duration:calc(1.4s / var(--speed))}.success .hermit-body{animation-name:hermit-joy}.error .hermit-body{animation-name:hermit-care}.hermit-mark{animation:hermit-mark calc(2.8s / var(--speed)) ease-in-out infinite}.thinking .hermit-eyes{animation:hermit-look calc(3.8s / var(--speed)) ease-in-out infinite}@keyframes hermit-idle{0%,100%{transform:translateY(0) rotate(-1deg) scale(1,1)}50%{transform:translateY(-1.2px) rotate(1deg) scale(1.015,.985)}}@keyframes hermit-blink{0%,42%,48%,100%{transform:scaleY(1)}45%{transform:scaleY(.12)}}@keyframes hermit-ask{0%,100%{transform:rotate(-2deg) translateY(0)}45%,65%{transform:rotate(6deg) translateY(-1.3px)}}@keyframes hermit-code{0%,100%{transform:translateY(0) rotate(-1.4deg)}50%{transform:translateY(-1px) rotate(1.4deg)}}@keyframes hermit-joy{0%,100%{transform:translateY(0) scale(1.02,.98)}45%{transform:translateY(-2.8px) rotate(-3deg) scale(.98,1.02)}70%{transform:translateY(-1px) rotate(2deg)}}@keyframes hermit-care{0%,70%,100%{transform:rotate(0)}78%{transform:rotate(-3deg)}88%{transform:rotate(3deg)}}@keyframes hermit-mark{0%,100%{transform:translateY(0) rotate(0)}50%{transform:translateY(-1.7px) rotate(3deg)}}@keyframes hermit-look{0%,25%,100%{transform:translate(0,0)}45%,70%{transform:translate(1.5px,-.6px)}}@media(prefers-reduced-motion:reduce){*{animation:none!important}}</style><rect x="8" y="12" width="68" height="47" rx="3" fill="#277f94"/><path d="m20 25 5 6-5 6m14 1h10" stroke="#a9d9d7" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none"/><g transform="translate(34 23)"><g class="hermit-body"><path d="M23 36v5m18-5v5" stroke="#a4574f" stroke-width="6" stroke-linecap="round"/><rect x="12" y="15" width="36" height="25" rx="4" fill="#f68042"/><g class="hermit-eyes" fill="none" stroke="#9957ad" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M29 23v5m10-5v5"/></g></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="7 5 52 42" class="idle" role="img" aria-label="Hermit 等待输入 无壳"><title>Hermit · 等待输入</title><style>svg{--speed:1}.hermit-body{transform-origin:32px 35px;animation:hermit-idle calc(3.8s / var(--speed)) cubic-bezier(.45,0,.55,1) infinite}.hermit-eyes{transform-box:fill-box;transform-origin:center;animation:hermit-blink calc(5.4s / var(--speed)) ease-in-out infinite}.question .hermit-body{animation-name:hermit-ask}.coding .hermit-eyes{animation:none}.coding .hermit-body{animation-name:hermit-code;animation-duration:calc(1.4s / var(--speed))}.success .hermit-body{animation-name:hermit-joy}.error .hermit-body{animation-name:hermit-care}.hermit-mark{animation:hermit-mark calc(2.8s / var(--speed)) ease-in-out infinite}.thinking .hermit-eyes{animation:hermit-look calc(3.8s / var(--speed)) ease-in-out infinite}@keyframes hermit-idle{0%,100%{transform:translateY(0) rotate(-1deg) scale(1,1)}50%{transform:translateY(-1.2px) rotate(1deg) scale(1.015,.985)}}@keyframes hermit-blink{0%,42%,48%,100%{transform:scaleY(1)}45%{transform:scaleY(.12)}}@keyframes hermit-ask{0%,100%{transform:rotate(-2deg) translateY(0)}45%,65%{transform:rotate(6deg) translateY(-1.3px)}}@keyframes hermit-code{0%,100%{transform:translateY(0) rotate(-1.4deg)}50%{transform:translateY(-1px) rotate(1.4deg)}}@keyframes hermit-joy{0%,100%{transform:translateY(0) scale(1.02,.98)}45%{transform:translateY(-2.8px) rotate(-3deg) scale(.98,1.02)}70%{transform:translateY(-1px) rotate(2deg)}}@keyframes hermit-care{0%,70%,100%{transform:rotate(0)}78%{transform:rotate(-3deg)}88%{transform:rotate(3deg)}}@keyframes hermit-mark{0%,100%{transform:translateY(0) rotate(0)}50%{transform:translateY(-1.7px) rotate(3deg)}}@keyframes hermit-look{0%,25%,100%{transform:translate(0,0)}45%,70%{transform:translate(1.5px,-.6px)}}@media(prefers-reduced-motion:reduce){*{animation:none!important}}</style><g class="hermit-body"><path d="M23 36v5m18-5v5" stroke="#a4574f" stroke-width="6" stroke-linecap="round"/><rect x="12" y="15" width="36" height="25" rx="4" fill="#f68042"/><g class="hermit-eyes" fill="none" stroke="#9957ad" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M29 23v5m10-5v5"/></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 80" class="question" role="img" aria-label="Hermit 请求问题 带壳"><title>Hermit · 请求问题</title><style>svg{--speed:1}.hermit-body{transform-origin:32px 35px;animation:hermit-idle calc(3.8s / var(--speed)) cubic-bezier(.45,0,.55,1) infinite}.hermit-eyes{transform-box:fill-box;transform-origin:center;animation:hermit-blink calc(5.4s / var(--speed)) ease-in-out infinite}.question .hermit-body{animation-name:hermit-ask}.coding .hermit-eyes{animation:none}.coding .hermit-body{animation-name:hermit-code;animation-duration:calc(1.4s / var(--speed))}.success .hermit-body{animation-name:hermit-joy}.error .hermit-body{animation-name:hermit-care}.hermit-mark{animation:hermit-mark calc(2.8s / var(--speed)) ease-in-out infinite}.thinking .hermit-eyes{animation:hermit-look calc(3.8s / var(--speed)) ease-in-out infinite}@keyframes hermit-idle{0%,100%{transform:translateY(0) rotate(-1deg) scale(1,1)}50%{transform:translateY(-1.2px) rotate(1deg) scale(1.015,.985)}}@keyframes hermit-blink{0%,42%,48%,100%{transform:scaleY(1)}45%{transform:scaleY(.12)}}@keyframes hermit-ask{0%,100%{transform:rotate(-2deg) translateY(0)}45%,65%{transform:rotate(6deg) translateY(-1.3px)}}@keyframes hermit-code{0%,100%{transform:translateY(0) rotate(-1.4deg)}50%{transform:translateY(-1px) rotate(1.4deg)}}@keyframes hermit-joy{0%,100%{transform:translateY(0) scale(1.02,.98)}45%{transform:translateY(-2.8px) rotate(-3deg) scale(.98,1.02)}70%{transform:translateY(-1px) rotate(2deg)}}@keyframes hermit-care{0%,70%,100%{transform:rotate(0)}78%{transform:rotate(-3deg)}88%{transform:rotate(3deg)}}@keyframes hermit-mark{0%,100%{transform:translateY(0) rotate(0)}50%{transform:translateY(-1.7px) rotate(3deg)}}@keyframes hermit-look{0%,25%,100%{transform:translate(0,0)}45%,70%{transform:translate(1.5px,-.6px)}}@media(prefers-reduced-motion:reduce){*{animation:none!important}}</style><rect x="8" y="12" width="68" height="47" rx="3" fill="#277f94"/><path d="m20 25 5 6-5 6m14 1h10" stroke="#a9d9d7" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none"/><g transform="translate(34 23)"><g class="hermit-body"><path d="M23 36v5m18-5v5" stroke="#a4574f" stroke-width="6" stroke-linecap="round"/><rect x="12" y="15" width="36" height="25" rx="4" fill="#f68042"/><g class="hermit-eyes" fill="none" stroke="#9957ad" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M30 22v4m10-4v4"/></g><path class="hermit-mark" d="M47 13c0-6 8-6 8-1 0 3-4 3-4 6m0 5v.1" stroke="#9957ad" stroke-width="3" stroke-linecap="round" fill="none"/></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="7 5 52 42" class="question" role="img" aria-label="Hermit 请求问题 无壳"><title>Hermit · 请求问题</title><style>svg{--speed:1}.hermit-body{transform-origin:32px 35px;animation:hermit-idle calc(3.8s / var(--speed)) cubic-bezier(.45,0,.55,1) infinite}.hermit-eyes{transform-box:fill-box;transform-origin:center;animation:hermit-blink calc(5.4s / var(--speed)) ease-in-out infinite}.question .hermit-body{animation-name:hermit-ask}.coding .hermit-eyes{animation:none}.coding .hermit-body{animation-name:hermit-code;animation-duration:calc(1.4s / var(--speed))}.success .hermit-body{animation-name:hermit-joy}.error .hermit-body{animation-name:hermit-care}.hermit-mark{animation:hermit-mark calc(2.8s / var(--speed)) ease-in-out infinite}.thinking .hermit-eyes{animation:hermit-look calc(3.8s / var(--speed)) ease-in-out infinite}@keyframes hermit-idle{0%,100%{transform:translateY(0) rotate(-1deg) scale(1,1)}50%{transform:translateY(-1.2px) rotate(1deg) scale(1.015,.985)}}@keyframes hermit-blink{0%,42%,48%,100%{transform:scaleY(1)}45%{transform:scaleY(.12)}}@keyframes hermit-ask{0%,100%{transform:rotate(-2deg) translateY(0)}45%,65%{transform:rotate(6deg) translateY(-1.3px)}}@keyframes hermit-code{0%,100%{transform:translateY(0) rotate(-1.4deg)}50%{transform:translateY(-1px) rotate(1.4deg)}}@keyframes hermit-joy{0%,100%{transform:translateY(0) scale(1.02,.98)}45%{transform:translateY(-2.8px) rotate(-3deg) scale(.98,1.02)}70%{transform:translateY(-1px) rotate(2deg)}}@keyframes hermit-care{0%,70%,100%{transform:rotate(0)}78%{transform:rotate(-3deg)}88%{transform:rotate(3deg)}}@keyframes hermit-mark{0%,100%{transform:translateY(0) rotate(0)}50%{transform:translateY(-1.7px) rotate(3deg)}}@keyframes hermit-look{0%,25%,100%{transform:translate(0,0)}45%,70%{transform:translate(1.5px,-.6px)}}@media(prefers-reduced-motion:reduce){*{animation:none!important}}</style><g class="hermit-body"><path d="M23 36v5m18-5v5" stroke="#a4574f" stroke-width="6" stroke-linecap="round"/><rect x="12" y="15" width="36" height="25" rx="4" fill="#f68042"/><g class="hermit-eyes" fill="none" stroke="#9957ad" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M30 22v4m10-4v4"/></g><path class="hermit-mark" d="M47 13c0-6 8-6 8-1 0 3-4 3-4 6m0 5v.1" stroke="#9957ad" stroke-width="3" stroke-linecap="round" fill="none"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 80" class="success" role="img" aria-label="Hermit 完成 带壳"><title>Hermit · 完成</title><style>svg{--speed:1}.hermit-body{transform-origin:32px 35px;animation:hermit-idle calc(3.8s / var(--speed)) cubic-bezier(.45,0,.55,1) infinite}.hermit-eyes{transform-box:fill-box;transform-origin:center;animation:hermit-blink calc(5.4s / var(--speed)) ease-in-out infinite}.question .hermit-body{animation-name:hermit-ask}.coding .hermit-eyes{animation:none}.coding .hermit-body{animation-name:hermit-code;animation-duration:calc(1.4s / var(--speed))}.success .hermit-body{animation-name:hermit-joy}.error .hermit-body{animation-name:hermit-care}.hermit-mark{animation:hermit-mark calc(2.8s / var(--speed)) ease-in-out infinite}.thinking .hermit-eyes{animation:hermit-look calc(3.8s / var(--speed)) ease-in-out infinite}@keyframes hermit-idle{0%,100%{transform:translateY(0) rotate(-1deg) scale(1,1)}50%{transform:translateY(-1.2px) rotate(1deg) scale(1.015,.985)}}@keyframes hermit-blink{0%,42%,48%,100%{transform:scaleY(1)}45%{transform:scaleY(.12)}}@keyframes hermit-ask{0%,100%{transform:rotate(-2deg) translateY(0)}45%,65%{transform:rotate(6deg) translateY(-1.3px)}}@keyframes hermit-code{0%,100%{transform:translateY(0) rotate(-1.4deg)}50%{transform:translateY(-1px) rotate(1.4deg)}}@keyframes hermit-joy{0%,100%{transform:translateY(0) scale(1.02,.98)}45%{transform:translateY(-2.8px) rotate(-3deg) scale(.98,1.02)}70%{transform:translateY(-1px) rotate(2deg)}}@keyframes hermit-care{0%,70%,100%{transform:rotate(0)}78%{transform:rotate(-3deg)}88%{transform:rotate(3deg)}}@keyframes hermit-mark{0%,100%{transform:translateY(0) rotate(0)}50%{transform:translateY(-1.7px) rotate(3deg)}}@keyframes hermit-look{0%,25%,100%{transform:translate(0,0)}45%,70%{transform:translate(1.5px,-.6px)}}@media(prefers-reduced-motion:reduce){*{animation:none!important}}</style><rect x="8" y="12" width="68" height="47" rx="3" fill="#277f94"/><path d="m20 25 5 6-5 6m14 1h10" stroke="#a9d9d7" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none"/><g transform="translate(34 23)"><g class="hermit-body"><path d="M23 36v5m18-5v5" stroke="#a4574f" stroke-width="6" stroke-linecap="round"/><rect x="12" y="15" width="36" height="25" rx="4" fill="#f68042"/><g class="hermit-eyes" fill="none" stroke="#9957ad" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="m27 25 3-3 3 3m5 0 3-3 3 3"/></g></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="7 5 52 42" class="success" role="img" aria-label="Hermit 完成 无壳"><title>Hermit · 完成</title><style>svg{--speed:1}.hermit-body{transform-origin:32px 35px;animation:hermit-idle calc(3.8s / var(--speed)) cubic-bezier(.45,0,.55,1) infinite}.hermit-eyes{transform-box:fill-box;transform-origin:center;animation:hermit-blink calc(5.4s / var(--speed)) ease-in-out infinite}.question .hermit-body{animation-name:hermit-ask}.coding .hermit-eyes{animation:none}.coding .hermit-body{animation-name:hermit-code;animation-duration:calc(1.4s / var(--speed))}.success .hermit-body{animation-name:hermit-joy}.error .hermit-body{animation-name:hermit-care}.hermit-mark{animation:hermit-mark calc(2.8s / var(--speed)) ease-in-out infinite}.thinking .hermit-eyes{animation:hermit-look calc(3.8s / var(--speed)) ease-in-out infinite}@keyframes hermit-idle{0%,100%{transform:translateY(0) rotate(-1deg) scale(1,1)}50%{transform:translateY(-1.2px) rotate(1deg) scale(1.015,.985)}}@keyframes hermit-blink{0%,42%,48%,100%{transform:scaleY(1)}45%{transform:scaleY(.12)}}@keyframes hermit-ask{0%,100%{transform:rotate(-2deg) translateY(0)}45%,65%{transform:rotate(6deg) translateY(-1.3px)}}@keyframes hermit-code{0%,100%{transform:translateY(0) rotate(-1.4deg)}50%{transform:translateY(-1px) rotate(1.4deg)}}@keyframes hermit-joy{0%,100%{transform:translateY(0) scale(1.02,.98)}45%{transform:translateY(-2.8px) rotate(-3deg) scale(.98,1.02)}70%{transform:translateY(-1px) rotate(2deg)}}@keyframes hermit-care{0%,70%,100%{transform:rotate(0)}78%{transform:rotate(-3deg)}88%{transform:rotate(3deg)}}@keyframes hermit-mark{0%,100%{transform:translateY(0) rotate(0)}50%{transform:translateY(-1.7px) rotate(3deg)}}@keyframes hermit-look{0%,25%,100%{transform:translate(0,0)}45%,70%{transform:translate(1.5px,-.6px)}}@media(prefers-reduced-motion:reduce){*{animation:none!important}}</style><g class="hermit-body"><path d="M23 36v5m18-5v5" stroke="#a4574f" stroke-width="6" stroke-linecap="round"/><rect x="12" y="15" width="36" height="25" rx="4" fill="#f68042"/><g class="hermit-eyes" fill="none" stroke="#9957ad" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="m27 25 3-3 3 3m5 0 3-3 3 3"/></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 80" class="thinking" role="img" aria-label="Hermit 思考 带壳"><title>Hermit · 思考</title><style>svg{--speed:1}.hermit-body{transform-origin:32px 35px;animation:hermit-idle calc(3.8s / var(--speed)) cubic-bezier(.45,0,.55,1) infinite}.hermit-eyes{transform-box:fill-box;transform-origin:center;animation:hermit-blink calc(5.4s / var(--speed)) ease-in-out infinite}.question .hermit-body{animation-name:hermit-ask}.coding .hermit-eyes{animation:none}.coding .hermit-body{animation-name:hermit-code;animation-duration:calc(1.4s / var(--speed))}.success .hermit-body{animation-name:hermit-joy}.error .hermit-body{animation-name:hermit-care}.hermit-mark{animation:hermit-mark calc(2.8s / var(--speed)) ease-in-out infinite}.thinking .hermit-eyes{animation:hermit-look calc(3.8s / var(--speed)) ease-in-out infinite}@keyframes hermit-idle{0%,100%{transform:translateY(0) rotate(-1deg) scale(1,1)}50%{transform:translateY(-1.2px) rotate(1deg) scale(1.015,.985)}}@keyframes hermit-blink{0%,42%,48%,100%{transform:scaleY(1)}45%{transform:scaleY(.12)}}@keyframes hermit-ask{0%,100%{transform:rotate(-2deg) translateY(0)}45%,65%{transform:rotate(6deg) translateY(-1.3px)}}@keyframes hermit-code{0%,100%{transform:translateY(0) rotate(-1.4deg)}50%{transform:translateY(-1px) rotate(1.4deg)}}@keyframes hermit-joy{0%,100%{transform:translateY(0) scale(1.02,.98)}45%{transform:translateY(-2.8px) rotate(-3deg) scale(.98,1.02)}70%{transform:translateY(-1px) rotate(2deg)}}@keyframes hermit-care{0%,70%,100%{transform:rotate(0)}78%{transform:rotate(-3deg)}88%{transform:rotate(3deg)}}@keyframes hermit-mark{0%,100%{transform:translateY(0) rotate(0)}50%{transform:translateY(-1.7px) rotate(3deg)}}@keyframes hermit-look{0%,25%,100%{transform:translate(0,0)}45%,70%{transform:translate(1.5px,-.6px)}}@media(prefers-reduced-motion:reduce){*{animation:none!important}}</style><rect x="8" y="12" width="68" height="47" rx="3" fill="#277f94"/><path d="m20 25 5 6-5 6m14 1h10" stroke="#a9d9d7" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none"/><g transform="translate(34 23)"><g class="hermit-body"><path d="M23 36v5m18-5v5" stroke="#a4574f" stroke-width="6" stroke-linecap="round"/><rect x="12" y="15" width="36" height="25" rx="4" fill="#f68042"/><g class="hermit-eyes" fill="none" stroke="#9957ad" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M31 22v3m10-3v3"/></g><path class="hermit-mark" d="M43 32v7h-8" stroke="#e9ba47" stroke-width="6" fill="none" stroke-linecap="round" stroke-linejoin="round"/></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="7 5 52 42" class="thinking" role="img" aria-label="Hermit 思考 无壳"><title>Hermit · 思考</title><style>svg{--speed:1}.hermit-body{transform-origin:32px 35px;animation:hermit-idle calc(3.8s / var(--speed)) cubic-bezier(.45,0,.55,1) infinite}.hermit-eyes{transform-box:fill-box;transform-origin:center;animation:hermit-blink calc(5.4s / var(--speed)) ease-in-out infinite}.question .hermit-body{animation-name:hermit-ask}.coding .hermit-eyes{animation:none}.coding .hermit-body{animation-name:hermit-code;animation-duration:calc(1.4s / var(--speed))}.success .hermit-body{animation-name:hermit-joy}.error .hermit-body{animation-name:hermit-care}.hermit-mark{animation:hermit-mark calc(2.8s / var(--speed)) ease-in-out infinite}.thinking .hermit-eyes{animation:hermit-look calc(3.8s / var(--speed)) ease-in-out infinite}@keyframes hermit-idle{0%,100%{transform:translateY(0) rotate(-1deg) scale(1,1)}50%{transform:translateY(-1.2px) rotate(1deg) scale(1.015,.985)}}@keyframes hermit-blink{0%,42%,48%,100%{transform:scaleY(1)}45%{transform:scaleY(.12)}}@keyframes hermit-ask{0%,100%{transform:rotate(-2deg) translateY(0)}45%,65%{transform:rotate(6deg) translateY(-1.3px)}}@keyframes hermit-code{0%,100%{transform:translateY(0) rotate(-1.4deg)}50%{transform:translateY(-1px) rotate(1.4deg)}}@keyframes hermit-joy{0%,100%{transform:translateY(0) scale(1.02,.98)}45%{transform:translateY(-2.8px) rotate(-3deg) scale(.98,1.02)}70%{transform:translateY(-1px) rotate(2deg)}}@keyframes hermit-care{0%,70%,100%{transform:rotate(0)}78%{transform:rotate(-3deg)}88%{transform:rotate(3deg)}}@keyframes hermit-mark{0%,100%{transform:translateY(0) rotate(0)}50%{transform:translateY(-1.7px) rotate(3deg)}}@keyframes hermit-look{0%,25%,100%{transform:translate(0,0)}45%,70%{transform:translate(1.5px,-.6px)}}@media(prefers-reduced-motion:reduce){*{animation:none!important}}</style><g class="hermit-body"><path d="M23 36v5m18-5v5" stroke="#a4574f" stroke-width="6" stroke-linecap="round"/><rect x="12" y="15" width="36" height="25" rx="4" fill="#f68042"/><g class="hermit-eyes" fill="none" stroke="#9957ad" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M31 22v3m10-3v3"/></g><path class="hermit-mark" d="M43 32v7h-8" stroke="#e9ba47" stroke-width="6" fill="none" stroke-linecap="round" stroke-linejoin="round"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,63 @@
|
|||||||
|
#ifndef CST_HERMIT_GLYPH_H
|
||||||
|
#define CST_HERMIT_GLYPH_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <wchar.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Product display name (user-visible). Binary install name is "hermit". */
|
||||||
|
#define HERMIT_NAME "Hermit"
|
||||||
|
#define HERMIT_BIN "hermit"
|
||||||
|
#define HERMIT_LEGACY_BIN "cst-agent"
|
||||||
|
#define HERMIT_CONFIG_DIR "hermit"
|
||||||
|
#define HERMIT_LEGACY_CONFIG_DIR "cst-agent"
|
||||||
|
|
||||||
|
/* Icon states mirror icons/agent-motion SVG set. */
|
||||||
|
typedef enum {
|
||||||
|
HERMIT_ST_IDLE = 0,
|
||||||
|
HERMIT_ST_QUESTION,
|
||||||
|
HERMIT_ST_THINKING,
|
||||||
|
HERMIT_ST_CODING,
|
||||||
|
HERMIT_ST_SUCCESS,
|
||||||
|
HERMIT_ST_ERROR,
|
||||||
|
HERMIT_ST_N
|
||||||
|
} hermit_state_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
HERMIT_ICON_AUTO = 0,
|
||||||
|
HERMIT_ICON_MINI,
|
||||||
|
HERMIT_ICON_FONT
|
||||||
|
} hermit_icon_mode_t;
|
||||||
|
|
||||||
|
/* HERMIT_<SUFFIX> then CST_<SUFFIX>. Never returns NULL (may be ""). */
|
||||||
|
const char *hermit_getenv(const char *suffix);
|
||||||
|
|
||||||
|
hermit_icon_mode_t hermit_icon_mode(void);
|
||||||
|
/* Effective mode after AUTO resolution (ascii forces MINI). */
|
||||||
|
hermit_icon_mode_t hermit_icon_mode_eff(int ascii);
|
||||||
|
|
||||||
|
/* Small badge UTF-8 (or ASCII). frame advances animation. */
|
||||||
|
const char *hermit_mini_badge(hermit_state_t st, int frame, int ascii);
|
||||||
|
|
||||||
|
/* Home shell crab: up to *nlines strings (not owned). Returns line count. */
|
||||||
|
int hermit_shell_art(hermit_state_t st, int with_shell, int ascii,
|
||||||
|
const char **lines, int max_lines);
|
||||||
|
|
||||||
|
/* Color roles for art cells: 0=body(crab) 1=shell 2=eye 3=leg 4=prompt 5=mark */
|
||||||
|
int hermit_shell_cell_role(hermit_state_t st, int with_shell, int row, int col);
|
||||||
|
|
||||||
|
/* PUA base U+E700; layout: shell*HERMIT_ST_N*HERMIT_FONT_FRAMES + st*FRAMES + frame */
|
||||||
|
#define HERMIT_FONT_PUA_BASE 0xE700
|
||||||
|
#define HERMIT_FONT_FRAMES 8
|
||||||
|
wchar_t hermit_font_codepoint(hermit_state_t st, int frame, int with_shell);
|
||||||
|
|
||||||
|
/* Map agent UI signals → icon state. */
|
||||||
|
hermit_state_t hermit_state_from_ui(int busy, int ask, int error);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
#define CST_VERSION_H
|
#define CST_VERSION_H
|
||||||
|
|
||||||
#define CST_VERSION_MAJOR 0
|
#define CST_VERSION_MAJOR 0
|
||||||
#define CST_VERSION_MINOR 9
|
#define CST_VERSION_MINOR 10
|
||||||
#define CST_VERSION_PATCH 34
|
#define CST_VERSION_PATCH 0
|
||||||
|
|
||||||
#define CST_VERSION_STR_(x) #x
|
#define CST_VERSION_STR_(x) #x
|
||||||
#define CST_VERSION_STR(x) CST_VERSION_STR_(x)
|
#define CST_VERSION_STR(x) CST_VERSION_STR_(x)
|
||||||
|
|||||||
@@ -44,7 +44,9 @@ run_make() {
|
|||||||
install_bin() {
|
install_bin() {
|
||||||
local prefix="$1" bin="$2"
|
local prefix="$1" bin="$2"
|
||||||
mkdir -p "$prefix/bin"
|
mkdir -p "$prefix/bin"
|
||||||
install -m 0755 "$bin" "$prefix/bin/cst-agent"
|
install -m 0755 "$bin" "$prefix/bin/hermit"
|
||||||
|
ln -sfr "$prefix/bin/hermit" "$prefix/bin/cst-agent" 2>/dev/null || \
|
||||||
|
ln -sf hermit "$prefix/bin/cst-agent"
|
||||||
}
|
}
|
||||||
|
|
||||||
install_helpers() {
|
install_helpers() {
|
||||||
@@ -60,7 +62,7 @@ EOF
|
|||||||
#!$(command -v bash)
|
#!$(command -v bash)
|
||||||
# Manage the parliament seat service (foreground under systemd, or --service).
|
# Manage the parliament seat service (foreground under systemd, or --service).
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
BIN="$prefix/bin/cst-agent"
|
BIN="$prefix/bin/hermit"
|
||||||
cmd="\${1:-status}"
|
cmd="\${1:-status}"
|
||||||
case "\$cmd" in
|
case "\$cmd" in
|
||||||
status)
|
status)
|
||||||
@@ -102,19 +104,24 @@ install_from_release() {
|
|||||||
if [[ -z "$ver" ]]; then
|
if [[ -z "$ver" ]]; then
|
||||||
ver="$(cst_version)"
|
ver="$(cst_version)"
|
||||||
fi
|
fi
|
||||||
local name="cst-agent-$ver-linux-$arch.tar.gz"
|
local name="hermit-$ver-linux-$arch.tar.gz"
|
||||||
|
# One-cycle legacy asset name fallback handled below if download fails.
|
||||||
local tmp
|
local tmp
|
||||||
tmp="$(mktemp -d /tmp/cst-rel.XXXXXX)"
|
tmp="$(mktemp -d /tmp/cst-rel.XXXXXX)"
|
||||||
# shellcheck disable=SC2046
|
# shellcheck disable=SC2046
|
||||||
|
local legacy="cst-agent-$ver-linux-$arch.tar.gz"
|
||||||
if ! cst_fetch "$tmp/$name" $(cst_release_asset_urls "$ver" "$name"); then
|
if ! cst_fetch "$tmp/$name" $(cst_release_asset_urls "$ver" "$name"); then
|
||||||
echo "release download failed for $name; falling back to source build" >&2
|
if ! cst_fetch "$tmp/$legacy" $(cst_release_asset_urls "$ver" "$legacy"); then
|
||||||
|
echo "release download failed; falling back to source build" >&2
|
||||||
rm -rf "$tmp"
|
rm -rf "$tmp"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
name="$legacy"
|
||||||
|
fi
|
||||||
tar -C "$tmp" -xzf "$tmp/$name"
|
tar -C "$tmp" -xzf "$tmp/$name"
|
||||||
local bin
|
local bin
|
||||||
bin="$(find "$tmp" -type f -name cst-agent | head -1)"
|
bin="$(find "$tmp" -type f \( -name hermit -o -name cst-agent \) | head -1)"
|
||||||
[[ -x "$bin" ]] || { echo "tarball missing cst-agent" >&2; rm -rf "$tmp"; return 1; }
|
[[ -x "$bin" ]] || { echo "tarball missing hermit/cst-agent" >&2; rm -rf "$tmp"; return 1; }
|
||||||
install_bin "$prefix" "$bin"
|
install_bin "$prefix" "$bin"
|
||||||
rm -rf "$tmp"
|
rm -rf "$tmp"
|
||||||
echo "==> installed release $ver ($arch) -> $prefix/bin/cst-agent"
|
echo "==> installed release $ver ($arch) -> $prefix/bin/cst-agent"
|
||||||
@@ -177,7 +184,7 @@ main() {
|
|||||||
if install_from_release "$prefix" "$dir"; then
|
if install_from_release "$prefix" "$dir"; then
|
||||||
install_helpers "$prefix" "$dir"
|
install_helpers "$prefix" "$dir"
|
||||||
echo
|
echo
|
||||||
echo "OK: $("$prefix/bin/cst-agent" --version) (release install)"
|
echo "OK: $("$prefix/bin/hermit" --version) (release install)"
|
||||||
case ":$PATH:" in
|
case ":$PATH:" in
|
||||||
*":$prefix/bin:"*) ;;
|
*":$prefix/bin:"*) ;;
|
||||||
*) echo "Add to PATH: export PATH=\"$prefix/bin:\$PATH\"" ;;
|
*) echo "Add to PATH: export PATH=\"$prefix/bin:\$PATH\"" ;;
|
||||||
@@ -209,14 +216,14 @@ main() {
|
|||||||
install_helpers "$prefix" "$dir"
|
install_helpers "$prefix" "$dir"
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "OK: $("$prefix/bin/cst-agent" --version)"
|
echo "OK: $("$prefix/bin/hermit" --version)"
|
||||||
case ":$PATH:" in
|
case ":$PATH:" in
|
||||||
*":$prefix/bin:"*) ;;
|
*":$prefix/bin:"*) ;;
|
||||||
*) echo "Add to PATH: export PATH=\"$prefix/bin:\$PATH\"" ;;
|
*) echo "Add to PATH: export PATH=\"$prefix/bin:\$PATH\"" ;;
|
||||||
esac
|
esac
|
||||||
echo "Run 'cst-agent' inside a project directory; upgrade with 'cst-agent-update'."
|
echo "Run 'hermit' (or 'cst-agent') in a project; upgrade with 'cst-agent-update'."
|
||||||
echo "Fast upgrade from Release: 'cst-agent-update --release'."
|
echo "Fast upgrade from Release: 'cst-agent-update --release'."
|
||||||
echo "Parliament seat service helper: 'cst-agent-service status|install-unit'."
|
echo "Parliament helper: 'cst-agent-service status|install-unit'."
|
||||||
echo "The first start asks for the provider and its API key and saves them."
|
echo "The first start asks for the provider and its API key and saves them."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,202 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Batch-convert icons/agent-motion SVG states into a color emoji-style font.
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
res/hermit-icons.ttf (CBDT/CBLC bitmap glyphs when fonttools works)
|
||||||
|
.gen/hermit_font_map.inc (PUA codepoint table for the TUI)
|
||||||
|
|
||||||
|
PUA layout matches cst_hermit_glyph.h:
|
||||||
|
U+E700 + (shell * N_STATE + state) * N_FRAME + frame
|
||||||
|
|
||||||
|
Requires (optional): rsvg-convert or cairosvg, pillow, fonttools.
|
||||||
|
Without them, still writes hermit_font_map.inc so FONT mode can emit codepoints.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import struct
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
ICONS = ROOT / "icons" / "agent-motion" / "icons"
|
||||||
|
OUT_TTF = ROOT / "res" / "hermit-icons.ttf"
|
||||||
|
OUT_INC = ROOT / ".gen" / "hermit_font_map.inc"
|
||||||
|
|
||||||
|
STATES = ["idle", "question", "thinking", "coding", "success", "error"]
|
||||||
|
N_FRAME = 8
|
||||||
|
PUA_BASE = 0xE700
|
||||||
|
|
||||||
|
|
||||||
|
def codepoint(shell: int, state: int, frame: int) -> int:
|
||||||
|
return PUA_BASE + (shell * len(STATES) + state) * N_FRAME + frame
|
||||||
|
|
||||||
|
|
||||||
|
def write_map_inc() -> None:
|
||||||
|
OUT_INC.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
lines = [
|
||||||
|
"/* Generated by scripts/build_hermit_font.py — do not edit. */",
|
||||||
|
f"#define HERMIT_FONT_PUA_BASE_GEN 0x{PUA_BASE:X}",
|
||||||
|
f"#define HERMIT_FONT_FRAMES_GEN {N_FRAME}",
|
||||||
|
f"#define HERMIT_FONT_STATES_GEN {len(STATES)}",
|
||||||
|
"/* state names: " + ", ".join(STATES) + " */",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
OUT_INC.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||||
|
print(f"wrote {OUT_INC}")
|
||||||
|
|
||||||
|
|
||||||
|
def render_svg(svg: Path, png: Path, size: int) -> bool:
|
||||||
|
png.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
try:
|
||||||
|
if subprocess.call(
|
||||||
|
["rsvg-convert", "-w", str(size), "-h", str(size), "-o", str(png), str(svg)],
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
) == 0:
|
||||||
|
return png.is_file()
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
import cairosvg # type: ignore
|
||||||
|
|
||||||
|
cairosvg.svg2png(url=str(svg), write_to=str(png), output_width=size, output_height=size)
|
||||||
|
return png.is_file()
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def build_ttf(png_by_cp: dict[int, Path]) -> bool:
|
||||||
|
try:
|
||||||
|
from fontTools.fontBuilder import FontBuilder # type: ignore
|
||||||
|
from fontTools.ttLib import TTFont # type: ignore
|
||||||
|
from fontTools.ttLib.tables.E_B_D_T_ import table_E_B_D_T_ # noqa: F401
|
||||||
|
from fontTools.ttLib.tables.C_B_D_T_ import table_C_B_D_T_ # noqa: F401
|
||||||
|
from fontTools.ttLib.tables.C_B_L_C_ import table_C_B_L_C_ # noqa: F401
|
||||||
|
from fontTools.ttLib.tables._g_l_y_f import Glyph # type: ignore
|
||||||
|
from fontTools.pens.ttGlyphPen import TTGlyphPen # type: ignore
|
||||||
|
except Exception as e:
|
||||||
|
print(f"fonttools CBDT path unavailable: {e}", file=sys.stderr)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Minimal TTF with empty glyf + Unicode cmap to PUA; color bitmaps via nanoemoji-less stub:
|
||||||
|
# Prefer shipping PNGs + cmap so terminals that map PUA→font face still get something.
|
||||||
|
try:
|
||||||
|
from PIL import Image # type: ignore
|
||||||
|
except Exception as e:
|
||||||
|
print(f"pillow required for font pack: {e}", file=sys.stderr)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Simplified: write a placeholder TTF that only has cmap entries; bitmaps
|
||||||
|
# need full CBDT authoring. For now embed as sbix-like custom is heavy —
|
||||||
|
# create a valid empty-outline font with correct cmap so install works, and
|
||||||
|
# document that full color CBDT needs `nanoemoji` when available.
|
||||||
|
glyph_order = [".notdef"]
|
||||||
|
cmap = {}
|
||||||
|
for cp, png in sorted(png_by_cp.items()):
|
||||||
|
name = f"u{cp:04X}"
|
||||||
|
glyph_order.append(name)
|
||||||
|
cmap[cp] = name
|
||||||
|
|
||||||
|
fb = FontBuilder(1024, isTTF=True)
|
||||||
|
fb.setupGlyphOrder(glyph_order)
|
||||||
|
fb.setupCharacterMap(cmap)
|
||||||
|
pen = TTGlyphPen(None)
|
||||||
|
empty = pen.glyph()
|
||||||
|
glyphs = {".notdef": empty}
|
||||||
|
for name in glyph_order[1:]:
|
||||||
|
glyphs[name] = empty
|
||||||
|
fb.setupGlyf(glyphs)
|
||||||
|
fb.setupHorizontalMetrics({n: (1024, 0) for n in glyph_order})
|
||||||
|
fb.setupHorizontalHeader(ascent=1024, descent=0)
|
||||||
|
fb.setupNameTable(
|
||||||
|
{
|
||||||
|
"familyName": "Hermit Icons",
|
||||||
|
"styleName": "Regular",
|
||||||
|
"uniqueFontIdentifier": "Hermit Icons",
|
||||||
|
"fullName": "Hermit Icons",
|
||||||
|
"psName": "HermitIcons",
|
||||||
|
"version": "0.10",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
fb.setupOS2()
|
||||||
|
fb.setupPost()
|
||||||
|
OUT_TTF.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
fb.save(str(OUT_TTF))
|
||||||
|
print(f"wrote {OUT_TTF} (cmap + empty outlines; color CBDT via nanoemoji optional)")
|
||||||
|
# Touch PNGs kept beside for tooling
|
||||||
|
dump = ROOT / "res" / "hermit-icon-frames"
|
||||||
|
dump.mkdir(parents=True, exist_ok=True)
|
||||||
|
for cp, png in png_by_cp.items():
|
||||||
|
target = dump / f"u{cp:04X}.png"
|
||||||
|
target.write_bytes(png.read_bytes())
|
||||||
|
print(f"wrote {len(png_by_cp)} frames under {dump}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def try_nanoemoji(svgs: list[Path]) -> bool:
|
||||||
|
"""If nanoemoji is installed, build a proper color font from SVGs."""
|
||||||
|
if not svgs:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
with tempfile.TemporaryDirectory() as td:
|
||||||
|
td = Path(td)
|
||||||
|
for i, s in enumerate(svgs):
|
||||||
|
(td / f"{i:03d}.svg").write_bytes(s.read_bytes())
|
||||||
|
out = td / "HermitIcons.ttf"
|
||||||
|
rc = subprocess.call(
|
||||||
|
["nanoemoji", f"--output_file={out}", *sorted(str(p) for p in td.glob("*.svg"))],
|
||||||
|
cwd=str(td),
|
||||||
|
)
|
||||||
|
if rc == 0 and out.is_file():
|
||||||
|
OUT_TTF.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
OUT_TTF.write_bytes(out.read_bytes())
|
||||||
|
print(f"wrote {OUT_TTF} via nanoemoji")
|
||||||
|
return True
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
print(f"nanoemoji failed: {e}", file=sys.stderr)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
write_map_inc()
|
||||||
|
if not ICONS.is_dir():
|
||||||
|
print(f"missing {ICONS}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
svg_list: list[Path] = []
|
||||||
|
png_by_cp: dict[int, Path] = {}
|
||||||
|
with tempfile.TemporaryDirectory() as td:
|
||||||
|
td = Path(td)
|
||||||
|
for shell, suffix in ((0, ""), (1, "-shell")):
|
||||||
|
for si, st in enumerate(STATES):
|
||||||
|
svg = ICONS / f"{st}{suffix}.svg"
|
||||||
|
if not svg.is_file():
|
||||||
|
print(f"skip missing {svg.name}", file=sys.stderr)
|
||||||
|
continue
|
||||||
|
svg_list.append(svg)
|
||||||
|
# One master frame per state for now; duplicate across N_FRAME.
|
||||||
|
png = td / f"{shell}_{st}.png"
|
||||||
|
if not render_svg(svg, png, 64):
|
||||||
|
print(f"render failed: {svg.name}", file=sys.stderr)
|
||||||
|
continue
|
||||||
|
for fr in range(N_FRAME):
|
||||||
|
png_by_cp[codepoint(shell, si, fr)] = png
|
||||||
|
|
||||||
|
if try_nanoemoji(svg_list):
|
||||||
|
return 0
|
||||||
|
if png_by_cp and build_ttf(png_by_cp):
|
||||||
|
return 0
|
||||||
|
print(
|
||||||
|
"font binary not built (install rsvg-convert/cairosvg + pillow + fonttools "
|
||||||
|
"or nanoemoji). Map .inc is ready for PUA emission.",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
return 0 # map is enough for compile
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=CST-Agent parliament seat service
|
Description=Hermit parliament seat service
|
||||||
Documentation=file://%h/CST-agent/docs/parliament.md
|
Documentation=file://%h/CST-agent/docs/parliament.md
|
||||||
After=network-online.target
|
After=network-online.target
|
||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
@@ -15,6 +15,8 @@ Environment=CST_INSTANCE_ID=%H
|
|||||||
# ~/.config/systemd/user/cst-agent-parliament.service.d/override.conf
|
# ~/.config/systemd/user/cst-agent-parliament.service.d/override.conf
|
||||||
# [Service]
|
# [Service]
|
||||||
# Environment=CST_REALM_PASSWORD=…
|
# Environment=CST_REALM_PASSWORD=…
|
||||||
|
# Also accepts HERMIT_* and config under ~/.config/hermit/
|
||||||
|
EnvironmentFile=-%h/.config/hermit/parliament.env
|
||||||
EnvironmentFile=-%h/.config/cst-agent/parliament.env
|
EnvironmentFile=-%h/.config/cst-agent/parliament.env
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ mk=(make CC="${T}gcc" AR="${T}ar" STATIC=1
|
|||||||
|
|
||||||
stamp=".gen/release-linux-$ARCH.stamp"
|
stamp=".gen/release-linux-$ARCH.stamp"
|
||||||
want="$(cst_build_stamp "${T}gcc")"
|
want="$(cst_build_stamp "${T}gcc")"
|
||||||
if [[ "${CST_FORCE_REBUILD:-0}" == 1 || ! -f cst-agent || ! -f "$stamp" ||
|
if [[ "${CST_FORCE_REBUILD:-0}" == 1 || ! -f hermit || ! -f "$stamp" ||
|
||||||
"$(cat "$stamp" 2>/dev/null || true)" != "$want" ]]; then
|
"$(cat "$stamp" 2>/dev/null || true)" != "$want" ]]; then
|
||||||
echo "==> clean rebuild ($ARCH)"
|
echo "==> clean rebuild ($ARCH)"
|
||||||
make clean >/dev/null
|
make clean >/dev/null
|
||||||
@@ -37,12 +37,12 @@ if [[ "${CST_FORCE_REBUILD:-0}" == 1 || ! -f cst-agent || ! -f "$stamp" ||
|
|||||||
else
|
else
|
||||||
echo "==> incremental rebuild ($ARCH, stamp match)"
|
echo "==> incremental rebuild ($ARCH, stamp match)"
|
||||||
fi
|
fi
|
||||||
"${mk[@]}" -j"$(nproc)" cst-agent
|
"${mk[@]}" -j"$(nproc)" hermit
|
||||||
mkdir -p .gen
|
mkdir -p .gen
|
||||||
printf '%s\n' "$want" >"$stamp"
|
printf '%s\n' "$want" >"$stamp"
|
||||||
|
|
||||||
if "${T}readelf" -l cst-agent | grep -q 'Requesting program interpreter'; then
|
if "${T}readelf" -l hermit | grep -q 'Requesting program interpreter'; then
|
||||||
echo "cst-agent is dynamically linked" >&2
|
echo "hermit is dynamically linked" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if [[ "${CST_LINUX_CHECK:-0}" == 1 ]]; then
|
if [[ "${CST_LINUX_CHECK:-0}" == 1 ]]; then
|
||||||
@@ -50,14 +50,18 @@ if [[ "${CST_LINUX_CHECK:-0}" == 1 ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
ver="$(cst_version)"
|
ver="$(cst_version)"
|
||||||
name="cst-agent-$ver-linux-$ARCH"
|
name="hermit-$ver-linux-$ARCH"
|
||||||
rel="dist/$name"
|
rel="dist/$name"
|
||||||
rm -rf "$rel"
|
rm -rf "$rel"
|
||||||
mkdir -p "$rel"
|
mkdir -p "$rel"
|
||||||
cp -f cst-agent "$rel/cst-agent"
|
cp -f hermit "$rel/hermit"
|
||||||
"${T}strip" "$rel/cst-agent"
|
ln -sfr "$rel/hermit" "$rel/cst-agent" 2>/dev/null || ln -sf hermit "$rel/cst-agent"
|
||||||
|
"${T}strip" "$rel/hermit"
|
||||||
cp -f README.md "$rel/README.md"
|
cp -f README.md "$rel/README.md"
|
||||||
echo "$ver" >"$rel/VERSION"
|
echo "$ver" >"$rel/VERSION"
|
||||||
tgz="$(cst_pack_tgz dist "$name")"
|
tgz="$(cst_pack_tgz dist "$name")"
|
||||||
|
# Compat alias package name for one release cycle
|
||||||
|
cp -f "$tgz" "dist/cst-agent-$ver-linux-$ARCH.tar.gz"
|
||||||
|
cst_sha256_write "dist/cst-agent-$ver-linux-$ARCH.tar.gz"
|
||||||
ls -la "$rel"
|
ls -la "$rel"
|
||||||
echo "$ROOT/$tgz"
|
echo "$ROOT/$tgz"
|
||||||
|
|||||||
@@ -59,9 +59,13 @@ json() { # CODE [ARGS...]: d = the JSON on stdin, ARGS in sys.argv[1:]
|
|||||||
python3 -c "import json,sys; d=json.load(sys.stdin); $code" "$@"
|
python3 -c "import json,sys; d=json.load(sys.stdin); $code" "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
BODY="Linux (static, musl, any distro): cst-agent-$ver-linux-{x86_64,x86,aarch64}.tar.gz -- extract and run ./cst-agent, or copy it onto PATH.
|
BODY="Hermit $ver
|
||||||
|
|
||||||
Windows x86_64: cst-agent-$ver-windows-x86_64-setup.exe installs per user (no admin rights) and adds PATH; cst-agent-$ver-windows-x86_64.zip is the portable build (install_windows.ps1 inside).
|
Linux (static, musl): hermit-$ver-linux-{x86_64,x86,aarch64}.tar.gz — extract and run ./hermit (cst-agent symlink included). Legacy cst-agent-*.tar.gz aliases may ship for one cycle.
|
||||||
|
|
||||||
|
Windows x86_64: hermit-$ver-windows-x86_64-setup.exe / .zip (cst-agent.exe symlink or copy when present).
|
||||||
|
|
||||||
|
Env: HERMIT_* preferred; CST_* still accepted. Config: ~/.config/hermit (falls back to ~/.config/cst-agent).
|
||||||
|
|
||||||
Every file has a .sha256 next to it."
|
Every file has a .sha256 next to it."
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,167 @@
|
|||||||
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
#include "cst_hermit_glyph.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
const char *hermit_getenv(const char *suffix) {
|
||||||
|
char hk[96], ck[96];
|
||||||
|
if (!suffix || !suffix[0])
|
||||||
|
return "";
|
||||||
|
snprintf(hk, sizeof(hk), "HERMIT_%s", suffix);
|
||||||
|
const char *e = getenv(hk);
|
||||||
|
if (e && e[0])
|
||||||
|
return e;
|
||||||
|
snprintf(ck, sizeof(ck), "CST_%s", suffix);
|
||||||
|
e = getenv(ck);
|
||||||
|
return e ? e : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
hermit_icon_mode_t hermit_icon_mode(void) {
|
||||||
|
const char *e = hermit_getenv("ICONS");
|
||||||
|
if (!e[0] || !strcmp(e, "auto"))
|
||||||
|
return HERMIT_ICON_AUTO;
|
||||||
|
if (!strcmp(e, "font") || !strcmp(e, "ttf"))
|
||||||
|
return HERMIT_ICON_FONT;
|
||||||
|
return HERMIT_ICON_MINI;
|
||||||
|
}
|
||||||
|
|
||||||
|
hermit_icon_mode_t hermit_icon_mode_eff(int ascii) {
|
||||||
|
if (ascii)
|
||||||
|
return HERMIT_ICON_MINI;
|
||||||
|
hermit_icon_mode_t m = hermit_icon_mode();
|
||||||
|
if (m != HERMIT_ICON_AUTO)
|
||||||
|
return m;
|
||||||
|
/* AUTO: font only when forced path exists or HERMIT_ICON_FONT=1. */
|
||||||
|
if (hermit_getenv("ICON_FONT")[0] == '1')
|
||||||
|
return HERMIT_ICON_FONT;
|
||||||
|
return HERMIT_ICON_MINI;
|
||||||
|
}
|
||||||
|
|
||||||
|
hermit_state_t hermit_state_from_ui(int busy, int ask, int error) {
|
||||||
|
if (error)
|
||||||
|
return HERMIT_ST_ERROR;
|
||||||
|
if (ask)
|
||||||
|
return HERMIT_ST_QUESTION;
|
||||||
|
if (busy)
|
||||||
|
return HERMIT_ST_CODING;
|
||||||
|
return HERMIT_ST_IDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
wchar_t hermit_font_codepoint(hermit_state_t st, int frame, int with_shell) {
|
||||||
|
if (st < 0 || st >= HERMIT_ST_N)
|
||||||
|
st = HERMIT_ST_IDLE;
|
||||||
|
if (frame < 0)
|
||||||
|
frame = 0;
|
||||||
|
frame %= HERMIT_FONT_FRAMES;
|
||||||
|
int shell = with_shell ? 1 : 0;
|
||||||
|
return (wchar_t)(HERMIT_FONT_PUA_BASE +
|
||||||
|
(shell * HERMIT_ST_N + (int)st) * HERMIT_FONT_FRAMES +
|
||||||
|
frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- minimal badge ------------------------------------------------------- */
|
||||||
|
|
||||||
|
const char *hermit_mini_badge(hermit_state_t st, int frame, int ascii) {
|
||||||
|
int f = frame < 0 ? 0 : frame;
|
||||||
|
if (ascii) {
|
||||||
|
switch (st) {
|
||||||
|
case HERMIT_ST_QUESTION:
|
||||||
|
return "||?";
|
||||||
|
case HERMIT_ST_ERROR:
|
||||||
|
return "\\/!";
|
||||||
|
case HERMIT_ST_SUCCESS:
|
||||||
|
return "||*";
|
||||||
|
case HERMIT_ST_THINKING:
|
||||||
|
return (f % 2) ? "~|" : "|~";
|
||||||
|
case HERMIT_ST_CODING:
|
||||||
|
case HERMIT_ST_IDLE:
|
||||||
|
default: {
|
||||||
|
static const char *const spin[] = {"||", ">_", "||", "-"};
|
||||||
|
return spin[f % 4];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch (st) {
|
||||||
|
case HERMIT_ST_QUESTION:
|
||||||
|
return "║║?";
|
||||||
|
case HERMIT_ST_ERROR:
|
||||||
|
return "╲╱!";
|
||||||
|
case HERMIT_ST_SUCCESS:
|
||||||
|
return "║║✦";
|
||||||
|
case HERMIT_ST_THINKING:
|
||||||
|
return (f % 2) ? "║~" : "~║";
|
||||||
|
case HERMIT_ST_CODING:
|
||||||
|
case HERMIT_ST_IDLE:
|
||||||
|
default: {
|
||||||
|
static const char *const spin[] = {"║║", ">_", "║║", "─"};
|
||||||
|
return spin[f % 4];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- home shell art (fixed grid) ----------------------------------------- */
|
||||||
|
/* 6 rows x 14 cols. Roles: 0 body 1 shell 2 eye 3 leg 4 prompt 5 empty/mark */
|
||||||
|
|
||||||
|
#define ART_H 6
|
||||||
|
#define ART_W 14
|
||||||
|
|
||||||
|
static const char *const ART_SHELL[ART_H] = {
|
||||||
|
" ######## ", /* shell top */
|
||||||
|
" ## >_ ## ", /* shell + prompt */
|
||||||
|
" ########## ", /* shell bottom */
|
||||||
|
" ###### ", /* body */
|
||||||
|
" #::## ", /* eyes :: on body */
|
||||||
|
" | | ", /* legs */
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *const ART_NOSHELL[ART_H] = {
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ###### ",
|
||||||
|
" #::## ",
|
||||||
|
" ###### ",
|
||||||
|
" | | ",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *const ART_SHELL_ASC[ART_H] = {
|
||||||
|
" ######## ",
|
||||||
|
" ## >_ ## ",
|
||||||
|
" ########## ",
|
||||||
|
" ###### ",
|
||||||
|
" #::## ",
|
||||||
|
" | | ",
|
||||||
|
};
|
||||||
|
|
||||||
|
int hermit_shell_art(hermit_state_t st, int with_shell, int ascii,
|
||||||
|
const char **lines, int max_lines) {
|
||||||
|
(void)st;
|
||||||
|
if (!lines || max_lines < 1)
|
||||||
|
return 0;
|
||||||
|
const char *const *src =
|
||||||
|
with_shell ? (ascii ? ART_SHELL_ASC : ART_SHELL) : ART_NOSHELL;
|
||||||
|
int n = ART_H < max_lines ? ART_H : max_lines;
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
lines[i] = src[i];
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
int hermit_shell_cell_role(hermit_state_t st, int with_shell, int row, int col) {
|
||||||
|
(void)st;
|
||||||
|
if (row < 0 || row >= ART_H || col < 0 || col >= ART_W)
|
||||||
|
return 5;
|
||||||
|
const char *const *src = with_shell ? ART_SHELL : ART_NOSHELL;
|
||||||
|
char c = src[row][col];
|
||||||
|
if (c == ' ' || !c)
|
||||||
|
return 5;
|
||||||
|
if (c == '>' || c == '_')
|
||||||
|
return 4;
|
||||||
|
if (c == ':')
|
||||||
|
return 2;
|
||||||
|
if (c == '|')
|
||||||
|
return 3;
|
||||||
|
if (with_shell && row <= 2)
|
||||||
|
return 1; /* shell */
|
||||||
|
return 0; /* body */
|
||||||
|
}
|
||||||
@@ -671,7 +671,7 @@ int main(int argc, char **argv) {
|
|||||||
do_migrate = 1;
|
do_migrate = 1;
|
||||||
break;
|
break;
|
||||||
case 'V':
|
case 'V':
|
||||||
printf("cst-agent %s\n", CST_VERSION);
|
printf("hermit %s\n", CST_VERSION);
|
||||||
cst_curl_cleanup();
|
cst_curl_cleanup();
|
||||||
return 0;
|
return 0;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -230,6 +230,7 @@ int os_rename_replace(const char *from, const char *to) {
|
|||||||
int os_config_dir(char *out, size_t out_sz) {
|
int os_config_dir(char *out, size_t out_sz) {
|
||||||
if (!out || out_sz < 8)
|
if (!out || out_sz < 8)
|
||||||
return -1;
|
return -1;
|
||||||
|
char neu[OS_PATH_MAX], old[OS_PATH_MAX];
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
const char *base = getenv("XDG_CONFIG_HOME");
|
const char *base = getenv("XDG_CONFIG_HOME");
|
||||||
if (!base || !os_path_is_abs(base))
|
if (!base || !os_path_is_abs(base))
|
||||||
@@ -238,20 +239,42 @@ int os_config_dir(char *out, size_t out_sz) {
|
|||||||
base = getenv("LOCALAPPDATA");
|
base = getenv("LOCALAPPDATA");
|
||||||
if (!base || !base[0])
|
if (!base || !base[0])
|
||||||
return -1;
|
return -1;
|
||||||
int n = snprintf(out, out_sz, "%s/cst-agent", base);
|
int n = snprintf(neu, sizeof(neu), "%s/hermit", base);
|
||||||
os_path_norm_slashes(out);
|
int o = snprintf(old, sizeof(old), "%s/cst-agent", base);
|
||||||
|
if (n < 0 || o < 0 || (size_t)n >= sizeof(neu) || (size_t)o >= sizeof(old))
|
||||||
|
return -1;
|
||||||
|
os_path_norm_slashes(neu);
|
||||||
|
os_path_norm_slashes(old);
|
||||||
#else
|
#else
|
||||||
const char *xdg = getenv("XDG_CONFIG_HOME");
|
const char *xdg = getenv("XDG_CONFIG_HOME");
|
||||||
const char *home = getenv("HOME");
|
const char *home = getenv("HOME");
|
||||||
int n;
|
int n, o;
|
||||||
if (xdg && xdg[0] == '/')
|
if (xdg && xdg[0] == '/') {
|
||||||
n = snprintf(out, out_sz, "%s/cst-agent", xdg);
|
n = snprintf(neu, sizeof(neu), "%s/hermit", xdg);
|
||||||
else if (home && home[0])
|
o = snprintf(old, sizeof(old), "%s/cst-agent", xdg);
|
||||||
n = snprintf(out, out_sz, "%s/.config/cst-agent", home);
|
} else if (home && home[0]) {
|
||||||
else
|
n = snprintf(neu, sizeof(neu), "%s/.config/hermit", home);
|
||||||
|
o = snprintf(old, sizeof(old), "%s/.config/cst-agent", home);
|
||||||
|
} else
|
||||||
|
return -1;
|
||||||
|
if (n < 0 || o < 0 || (size_t)n >= sizeof(neu) || (size_t)o >= sizeof(old))
|
||||||
return -1;
|
return -1;
|
||||||
#endif
|
#endif
|
||||||
if (n < 0 || (size_t)n >= out_sz)
|
/* Prefer hermit/; fall back to legacy cst-agent/ when hermit is absent. */
|
||||||
|
{
|
||||||
|
struct stat st;
|
||||||
|
if (stat(neu, &st) == 0 && S_ISDIR(st.st_mode)) {
|
||||||
|
if (snprintf(out, out_sz, "%s", neu) < 0 || strlen(neu) >= out_sz)
|
||||||
|
return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (stat(old, &st) == 0 && S_ISDIR(st.st_mode)) {
|
||||||
|
if (snprintf(out, out_sz, "%s", old) < 0 || strlen(old) >= out_sz)
|
||||||
|
return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (snprintf(out, out_sz, "%s", neu) < 0 || strlen(neu) >= out_sz)
|
||||||
return -1;
|
return -1;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -298,10 +321,15 @@ static void tmp_pick(void) {
|
|||||||
snprintf(cand[nc++], OS_PATH_MAX, "%s/tmp", prefix);
|
snprintf(cand[nc++], OS_PATH_MAX, "%s/tmp", prefix);
|
||||||
snprintf(cand[nc++], OS_PATH_MAX, "/tmp");
|
snprintf(cand[nc++], OS_PATH_MAX, "/tmp");
|
||||||
snprintf(cand[nc++], OS_PATH_MAX, "/var/tmp");
|
snprintf(cand[nc++], OS_PATH_MAX, "/var/tmp");
|
||||||
if (cache && cache[0] == '/')
|
if (cache && cache[0] == '/') {
|
||||||
|
snprintf(cand[nc++], OS_PATH_MAX, "%s/hermit/tmp", cache);
|
||||||
|
if (nc < 5)
|
||||||
snprintf(cand[nc++], OS_PATH_MAX, "%s/cst-agent/tmp", cache);
|
snprintf(cand[nc++], OS_PATH_MAX, "%s/cst-agent/tmp", cache);
|
||||||
else if (home && home[0] == '/')
|
} else if (home && home[0] == '/') {
|
||||||
|
snprintf(cand[nc++], OS_PATH_MAX, "%s/.cache/hermit/tmp", home);
|
||||||
|
if (nc < 5)
|
||||||
snprintf(cand[nc++], OS_PATH_MAX, "%s/.cache/cst-agent/tmp", home);
|
snprintf(cand[nc++], OS_PATH_MAX, "%s/.cache/cst-agent/tmp", home);
|
||||||
|
}
|
||||||
for (int i = 0; i < nc; i++) {
|
for (int i = 0; i < nc; i++) {
|
||||||
tmp_trim(cand[i]);
|
tmp_trim(cand[i]);
|
||||||
if (i == nc - 1)
|
if (i == nc - 1)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
#include "cst_tools.h"
|
#include "cst_tools.h"
|
||||||
#include "cst_util.h"
|
#include "cst_util.h"
|
||||||
#include "cst_version.h"
|
#include "cst_version.h"
|
||||||
|
#include "cst_hermit_glyph.h"
|
||||||
#include "os_compat.h"
|
#include "os_compat.h"
|
||||||
#include "sashidown.h"
|
#include "sashidown.h"
|
||||||
|
|
||||||
@@ -496,18 +497,18 @@ static char *xstrndup(const char *s, size_t n) {
|
|||||||
|
|
||||||
/* --- theme ---------------------------------------------------------------- */
|
/* --- theme ---------------------------------------------------------------- */
|
||||||
|
|
||||||
/* Dark panel chrome — cool steel accents (not peach OpenCode clone). */
|
/* Hermit palette: brand teal, crab orange, eye purple (design mock). */
|
||||||
static void theme_init(void) {
|
static void theme_init(void) {
|
||||||
const char *nc = getenv("NO_COLOR");
|
const char *nc = getenv("NO_COLOR");
|
||||||
g_nocolor = nc && nc[0];
|
g_nocolor = nc && nc[0];
|
||||||
const char *asc = getenv("CST_ASCII");
|
const char *asc = hermit_getenv("ASCII");
|
||||||
#if defined(__ANDROID__)
|
#if defined(__ANDROID__)
|
||||||
g_utf8 = 1; /* bionic is UTF-8 only; nl_langinfo needs API 26 */
|
g_utf8 = 1; /* bionic is UTF-8 only; nl_langinfo needs API 26 */
|
||||||
#elif !defined(_WIN32)
|
#elif !defined(_WIN32)
|
||||||
const char *cs = nl_langinfo(CODESET);
|
const char *cs = nl_langinfo(CODESET);
|
||||||
g_utf8 = cs && (strcasecmp(cs, "UTF-8") == 0 || strcasecmp(cs, "utf8") == 0);
|
g_utf8 = cs && (strcasecmp(cs, "UTF-8") == 0 || strcasecmp(cs, "utf8") == 0);
|
||||||
#endif
|
#endif
|
||||||
g_ascii = asc && asc[0] ? asc[0] != '0' : !g_utf8;
|
g_ascii = asc[0] ? asc[0] != '0' : !g_utf8;
|
||||||
if (!HAS_COLOR())
|
if (!HAS_COLOR())
|
||||||
return;
|
return;
|
||||||
start_color();
|
start_color();
|
||||||
@@ -515,54 +516,62 @@ static void theme_init(void) {
|
|||||||
|
|
||||||
short primary = COLOR_CYAN;
|
short primary = COLOR_CYAN;
|
||||||
short panel = COLOR_BLACK;
|
short panel = COLOR_BLACK;
|
||||||
short muted = COLOR_CYAN;
|
short muted = COLOR_WHITE;
|
||||||
short success = COLOR_GREEN;
|
short success = COLOR_GREEN;
|
||||||
short err = COLOR_RED;
|
short err = COLOR_RED;
|
||||||
short assist = COLOR_CYAN;
|
short assist = COLOR_MAGENTA;
|
||||||
short text = COLOR_WHITE;
|
short text = COLOR_WHITE;
|
||||||
short box = COLOR_BLACK, side = COLOR_BLACK;
|
short box = COLOR_BLACK, side = COLOR_BLACK;
|
||||||
short accent = COLOR_BLUE, bright = COLOR_WHITE, logo_dim = COLOR_CYAN;
|
short accent = COLOR_CYAN, bright = COLOR_WHITE, logo_dim = COLOR_CYAN;
|
||||||
|
short tool = COLOR_YELLOW;
|
||||||
|
short prompt_c = COLOR_CYAN;
|
||||||
|
|
||||||
if (COLORS >= 256) {
|
if (COLORS >= 256) {
|
||||||
box = 235;
|
box = 235;
|
||||||
side = 234;
|
side = 234;
|
||||||
accent = 75; /* #5c9cf5 steel blue */
|
accent = 75; /* #277F94 brand teal */
|
||||||
bright = 255;
|
bright = 255;
|
||||||
logo_dim = 242;
|
logo_dim = 242;
|
||||||
primary = 75; /* brand/user accent: blue, not peach */
|
primary = 75;
|
||||||
panel = 233; /* #121212 */
|
panel = 233;
|
||||||
muted = 244;
|
muted = 244;
|
||||||
success = 114;
|
success = 114;
|
||||||
err = 167;
|
err = 167;
|
||||||
assist = 74; /* soft cyan assistant */
|
assist = 177; /* #9957AD eye purple */
|
||||||
text = 252; /* brighter body for readability */
|
tool = 216; /* #F68042 crab orange */
|
||||||
|
prompt_c = 152; /* #A9D9D7 prompt cyan */
|
||||||
|
text = 252;
|
||||||
if (can_change_color()) {
|
if (can_change_color()) {
|
||||||
init_color(75, 430, 660, 996); /* #6ea8fe brand */
|
init_color(75, 153, 498, 580); /* #277F94 brand */
|
||||||
init_color(233, 55, 58, 70); /* #0e0f12 deeper panel */
|
init_color(152, 663, 851, 843); /* #A9D9D7 prompt */
|
||||||
init_color(244, 502, 520, 560); /* cool muted */
|
init_color(216, 965, 502, 259); /* #F68042 crab */
|
||||||
init_color(252, 820, 830, 850); /* body */
|
init_color(177, 600, 341, 678); /* #9957AD eye */
|
||||||
|
init_color(167, 850, 320, 320); /* error red */
|
||||||
|
init_color(168, 643, 341, 310); /* #A4574F brick legs */
|
||||||
|
init_color(233, 55, 58, 70); /* #0e0f12 panel */
|
||||||
|
init_color(244, 502, 520, 560); /* muted */
|
||||||
|
init_color(252, 820, 830, 850); /* body text */
|
||||||
init_color(114, 400, 750, 450);
|
init_color(114, 400, 750, 450);
|
||||||
init_color(167, 850, 320, 320);
|
init_color(235, 100, 105, 120);
|
||||||
init_color(74, 380, 720, 780);
|
init_color(234, 78, 82, 96);
|
||||||
init_color(235, 100, 105, 120); /* raised box */
|
|
||||||
init_color(234, 78, 82, 96); /* sidebar */
|
|
||||||
init_color(216, 980, 720, 420); /* warm tip only */
|
|
||||||
}
|
}
|
||||||
} else if (can_change_color() && COLORS >= 8) {
|
} else if (can_change_color() && COLORS >= 8) {
|
||||||
init_color(COLOR_BLACK, 55, 58, 70);
|
init_color(COLOR_BLACK, 55, 58, 70);
|
||||||
init_color(COLOR_CYAN, 430, 660, 996);
|
init_color(COLOR_CYAN, 153, 498, 580);
|
||||||
init_color(COLOR_WHITE, 820, 830, 850);
|
init_color(COLOR_WHITE, 820, 830, 850);
|
||||||
init_color(COLOR_GREEN, 400, 750, 450);
|
init_color(COLOR_GREEN, 400, 750, 450);
|
||||||
init_color(COLOR_RED, 850, 320, 320);
|
init_color(COLOR_RED, 850, 320, 320);
|
||||||
init_color(COLOR_BLUE, 380, 720, 780);
|
init_color(COLOR_MAGENTA, 600, 341, 678);
|
||||||
init_color(COLOR_YELLOW, 980, 720, 420);
|
init_color(COLOR_YELLOW, 965, 502, 259);
|
||||||
panel = COLOR_BLACK;
|
panel = COLOR_BLACK;
|
||||||
primary = COLOR_CYAN;
|
primary = COLOR_CYAN;
|
||||||
muted = COLOR_WHITE;
|
muted = COLOR_WHITE;
|
||||||
text = COLOR_WHITE;
|
text = COLOR_WHITE;
|
||||||
success = COLOR_GREEN;
|
success = COLOR_GREEN;
|
||||||
err = COLOR_RED;
|
err = COLOR_RED;
|
||||||
assist = COLOR_BLUE;
|
assist = COLOR_MAGENTA;
|
||||||
|
tool = COLOR_YELLOW;
|
||||||
|
prompt_c = COLOR_CYAN;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Header is dark too — primary only as text accent (not a bright bar). */
|
/* Header is dark too — primary only as text accent (not a bright bar). */
|
||||||
@@ -574,11 +583,11 @@ static void theme_init(void) {
|
|||||||
init_pair(CP_MUTED, muted, panel);
|
init_pair(CP_MUTED, muted, panel);
|
||||||
init_pair(CP_SUCCESS, success, panel);
|
init_pair(CP_SUCCESS, success, panel);
|
||||||
init_pair(CP_ERROR, err, panel);
|
init_pair(CP_ERROR, err, panel);
|
||||||
init_pair(CP_TOOL, primary, panel);
|
init_pair(CP_TOOL, tool, panel);
|
||||||
init_pair(CP_PANEL, text, panel);
|
init_pair(CP_PANEL, text, panel);
|
||||||
init_pair(CP_SGR_GREEN, success, panel);
|
init_pair(CP_SGR_GREEN, success, panel);
|
||||||
init_pair(CP_SGR_YELLOW, primary, panel);
|
init_pair(CP_SGR_YELLOW, tool, panel);
|
||||||
init_pair(CP_SGR_CYAN, assist, panel);
|
init_pair(CP_SGR_CYAN, prompt_c, panel);
|
||||||
if (COLORS >= 256)
|
if (COLORS >= 256)
|
||||||
init_pair(CP_SGR_MAGENTA, 176, panel);
|
init_pair(CP_SGR_MAGENTA, 176, panel);
|
||||||
else
|
else
|
||||||
@@ -2687,20 +2696,87 @@ static void draw_todo(tui_t *t) {
|
|||||||
|
|
||||||
/* --- start screen ---------------------------------------------------------- */
|
/* --- start screen ---------------------------------------------------------- */
|
||||||
|
|
||||||
/* 3-row block font for "cstagent"; each glyph is 4 columns wide. */
|
/* 3-row block font for "HERMIT"; each glyph is 4 columns wide. */
|
||||||
static const char *const LOGO[8][3] = {
|
static const char *const LOGO[6][3] = {
|
||||||
{"█▀▀▀", "█ ", "▀▀▀▀"}, /* c */
|
{"█ █", "█▀▀█", "█ █"}, /* H */
|
||||||
{"█▀▀▀", "▀▀▀█", "▀▀▀▀"}, /* s */
|
{"█▀▀▀", "█▀▀ ", "▀▀▀▀"}, /* E */
|
||||||
{"▀█▀▀", " █ ", " ▀▀▀"}, /* t */
|
{"█▀▀█", "█▀▀█", "▀ ▀"}, /* R */
|
||||||
{"█▀▀█", "█▀▀█", "▀ ▀"}, /* a */
|
{"█▀▄█", "█ █", "▀ ▀"}, /* M */
|
||||||
{"█▀▀▀", "█ ▀█", "▀▀▀▀"}, /* g */
|
{"▀█▀▀", " █ ", " ▀▀▀"}, /* I */
|
||||||
{"█▀▀▀", "█▀▀ ", "▀▀▀▀"}, /* e */
|
{"▀█▀▀", " █ ", " ▀▀▀"}, /* T */
|
||||||
{"█▀▀▄", "█ █", "▀ ▀"}, /* n */
|
|
||||||
{"▀█▀▀", " █ ", " ▀▀▀"}, /* t */
|
|
||||||
};
|
};
|
||||||
#define LOGO_W (8 * 5 - 1)
|
#define LOGO_N 6
|
||||||
|
#define LOGO_W (LOGO_N * 5 - 1)
|
||||||
#define LOGO_H 3
|
#define LOGO_H 3
|
||||||
|
|
||||||
|
static int hermit_role_pair(int role) {
|
||||||
|
switch (role) {
|
||||||
|
case 0:
|
||||||
|
return CP_TOOL; /* body */
|
||||||
|
case 1:
|
||||||
|
return CP_ACCENT; /* shell */
|
||||||
|
case 2:
|
||||||
|
return CP_ASSIST; /* eye */
|
||||||
|
case 3:
|
||||||
|
return COLORS >= 256 ? CP_MUTED : CP_ERROR; /* brick legs */
|
||||||
|
case 4:
|
||||||
|
return CP_SGR_CYAN; /* >_ */
|
||||||
|
default:
|
||||||
|
return CP_MUTED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_hermit_icon(WINDOW *win, int y0, int x0, hermit_state_t st,
|
||||||
|
int with_shell, int frame) {
|
||||||
|
hermit_icon_mode_t mode = hermit_icon_mode_eff(g_ascii);
|
||||||
|
if (mode == HERMIT_ICON_FONT) {
|
||||||
|
wchar_t wc = hermit_font_codepoint(st, frame, with_shell);
|
||||||
|
cchar_t cc;
|
||||||
|
wchar_t wstr[2] = {wc, 0};
|
||||||
|
if (setcchar(&cc, wstr, 0, 0, NULL) == OK) {
|
||||||
|
if (HAS_COLOR())
|
||||||
|
wattron(win, COLOR_PAIR(CP_TOOL));
|
||||||
|
mvwadd_wch(win, y0, x0, &cc);
|
||||||
|
if (HAS_COLOR())
|
||||||
|
wattroff(win, COLOR_PAIR(CP_TOOL));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const char *lines[8];
|
||||||
|
int n = hermit_shell_art(st, with_shell, g_ascii, lines, 8);
|
||||||
|
for (int r = 0; r < n; r++) {
|
||||||
|
const char *row = lines[r];
|
||||||
|
for (int c = 0; row[c]; c++) {
|
||||||
|
if (row[c] == ' ')
|
||||||
|
continue;
|
||||||
|
char ch[2] = {row[c], 0};
|
||||||
|
int pair = hermit_role_pair(hermit_shell_cell_role(st, with_shell, r, c));
|
||||||
|
draw_ansi_at(win, y0 + r, x0 + c, 1, ch, pair, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_hermit_badge(WINDOW *win, int y, int x, const tui_t *t) {
|
||||||
|
hermit_state_t st = hermit_state_from_ui(
|
||||||
|
t->busy, t->ask_pending || t->awaiting_perm, 0);
|
||||||
|
hermit_icon_mode_t mode = hermit_icon_mode_eff(g_ascii);
|
||||||
|
if (mode == HERMIT_ICON_FONT) {
|
||||||
|
wchar_t wc = hermit_font_codepoint(st, t->spin, 0);
|
||||||
|
cchar_t cc;
|
||||||
|
wchar_t wstr[2] = {wc, 0};
|
||||||
|
if (setcchar(&cc, wstr, 0, 0, NULL) == OK) {
|
||||||
|
if (HAS_COLOR())
|
||||||
|
wattron(win, COLOR_PAIR(CP_TOOL));
|
||||||
|
mvwadd_wch(win, y, x, &cc);
|
||||||
|
if (HAS_COLOR())
|
||||||
|
wattroff(win, COLOR_PAIR(CP_TOOL));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const char *b = hermit_mini_badge(st, t->spin, g_ascii);
|
||||||
|
draw_ansi_at(win, y, x, (int)strlen(b), b, CP_TOOL, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
static const char *const TIPS[] = {
|
static const char *const TIPS[] = {
|
||||||
"Tab switches between Build and Plan (read-only) mode",
|
"Tab switches between Build and Plan (read-only) mode",
|
||||||
"Shift+Tab cycles approval: ask, auto, yolo",
|
"Shift+Tab cycles approval: ask, auto, yolo",
|
||||||
@@ -2720,40 +2796,51 @@ static void draw_home(tui_t *t) {
|
|||||||
getmaxyx(t->logw, h, w);
|
getmaxyx(t->logw, h, w);
|
||||||
int box_top = t->in_y - t->log_y;
|
int box_top = t->in_y - t->log_y;
|
||||||
int box_bot = box_top + t->in_h;
|
int box_bot = box_top + t->in_h;
|
||||||
/* Tight home stack: wordmark (default) or CST_LOGO=block, tagline, chip. */
|
|
||||||
int brand_y = box_top - 2;
|
int brand_y = box_top - 2;
|
||||||
if (brand_y < 0)
|
if (brand_y < 0)
|
||||||
brand_y = 0;
|
brand_y = 0;
|
||||||
int want_block = 0;
|
int want_block = 0;
|
||||||
{
|
{
|
||||||
const char *lb = getenv("CST_LOGO");
|
const char *lb = hermit_getenv("LOGO");
|
||||||
want_block = lb && !strcmp(lb, "block");
|
want_block = lb[0] && !strcmp(lb, "block");
|
||||||
}
|
}
|
||||||
|
/* Crab on the right of the brand stack when there is room. */
|
||||||
|
int crab_w = 14, crab_h = 6;
|
||||||
|
int crab_x = w - crab_w - 2;
|
||||||
|
int show_crab = crab_x > LOGO_W + 4 && box_top >= crab_h + 1;
|
||||||
if (want_block && !(g_ascii || !g_utf8 || g_logo_text) &&
|
if (want_block && !(g_ascii || !g_utf8 || g_logo_text) &&
|
||||||
box_top - LOGO_H - 2 >= 0 && w >= LOGO_W) {
|
box_top - LOGO_H - 2 >= 0 && w >= LOGO_W) {
|
||||||
int x0 = (w - LOGO_W) / 2, y0 = box_top - LOGO_H - 2;
|
int x0 = show_crab ? (crab_x - LOGO_W) / 2 : (w - LOGO_W) / 2;
|
||||||
for (int g = 0; g < 8; g++)
|
if (x0 < 0)
|
||||||
|
x0 = 0;
|
||||||
|
int y0 = box_top - LOGO_H - 2;
|
||||||
|
for (int g = 0; g < LOGO_N; g++)
|
||||||
for (int r = 0; r < LOGO_H; r++)
|
for (int r = 0; r < LOGO_H; r++)
|
||||||
draw_ansi_at(t->logw, y0 + r, x0 + g * 5, 4, LOGO[g][r],
|
draw_ansi_at(t->logw, y0 + r, x0 + g * 5, 4, LOGO[g][r], CP_ACCENT, 0,
|
||||||
g < 3 ? CP_ACCENT : CP_BRIGHT, 0, 0);
|
0);
|
||||||
brand_y = y0 + LOGO_H;
|
brand_y = y0 + LOGO_H;
|
||||||
} else {
|
} else {
|
||||||
const char *mark = "CST-agent";
|
const char *mark = "HERMIT";
|
||||||
int mw = (int)strlen(mark);
|
int mw = (int)strlen(mark);
|
||||||
int x = (w - mw) / 2;
|
int x = show_crab ? (crab_x - mw) / 2 : (w - mw) / 2;
|
||||||
if (x < 0)
|
if (x < 0)
|
||||||
x = 0;
|
x = 0;
|
||||||
if (brand_y >= 1)
|
if (brand_y >= 1)
|
||||||
brand_y -= 1;
|
brand_y -= 1;
|
||||||
draw_ansi_at(t->logw, brand_y, x, 3, "CST", CP_ACCENT, 1, 0);
|
draw_ansi_at(t->logw, brand_y, x, mw, mark, CP_ACCENT, 1, 0);
|
||||||
draw_ansi_at(t->logw, brand_y, x + 3, mw - 3, "-agent", CP_BRIGHT, 1, 0);
|
}
|
||||||
|
if (show_crab) {
|
||||||
|
int cy = box_top - crab_h;
|
||||||
|
if (cy < 0)
|
||||||
|
cy = 0;
|
||||||
|
draw_hermit_icon(t->logw, cy, crab_x, HERMIT_ST_IDLE, 1, t->spin);
|
||||||
}
|
}
|
||||||
int ty = brand_y + 1;
|
int ty = brand_y + 1;
|
||||||
if (ty < box_top) {
|
if (ty < box_top) {
|
||||||
const char *tag =
|
const char *tag =
|
||||||
g_ascii ? "code . plan . parliament" : "code · plan · parliament";
|
g_ascii ? "code . plan . parliament" : "code · plan · parliament";
|
||||||
int tw = (int)strlen(tag);
|
int tw = (int)strlen(tag);
|
||||||
int x = (w - tw) / 2;
|
int x = show_crab ? (crab_x - tw) / 2 : (w - tw) / 2;
|
||||||
if (x < 0)
|
if (x < 0)
|
||||||
x = 0;
|
x = 0;
|
||||||
draw_ansi_at(t->logw, ty, x, tw, tag, CP_MUTED, 0, 0);
|
draw_ansi_at(t->logw, ty, x, tw, tag, CP_MUTED, 0, 0);
|
||||||
@@ -2772,9 +2859,10 @@ static void draw_home(tui_t *t) {
|
|||||||
}
|
}
|
||||||
int right = t->in_x - t->log_x + t->in_w;
|
int right = t->in_x - t->log_x + t->in_w;
|
||||||
if (box_bot < h) {
|
if (box_bot < h) {
|
||||||
char hint[96];
|
char hint[128];
|
||||||
snprintf(hint, sizeof(hint),
|
snprintf(hint, sizeof(hint),
|
||||||
"\033[97mtab\033[39m modes \033[97mctrl+p\033[39m commands");
|
"\033[97mtab\033[39m modes \033[97mctrl+p\033[39m commands "
|
||||||
|
"\033[97mshift+tab\033[39m approval");
|
||||||
int hw = vis_cols(hint);
|
int hw = vis_cols(hint);
|
||||||
if (right - hw >= 0)
|
if (right - hw >= 0)
|
||||||
draw_ansi_at(t->logw, box_bot, right - hw, hw, hint, CP_MUTED, 0, 0);
|
draw_ansi_at(t->logw, box_bot, right - hw, hw, hint, CP_MUTED, 0, 0);
|
||||||
@@ -2785,7 +2873,7 @@ static void draw_home(tui_t *t) {
|
|||||||
int tw = vis_cols(tip);
|
int tw = vis_cols(tip);
|
||||||
if (tw > w - 4)
|
if (tw > w - 4)
|
||||||
tw = w - 4;
|
tw = w - 4;
|
||||||
draw_ansi_at(t->logw, box_bot + 2, (w - tw) / 2, tw, tip, CP_MUTED, 0, 0);
|
draw_ansi_at(t->logw, box_bot + 2, (w - tw) / 2, tw, tip, CP_TOOL, 0, 0);
|
||||||
}
|
}
|
||||||
const char *ext = cst_extensions_report();
|
const char *ext = cst_extensions_report();
|
||||||
if (ext[0] && box_bot + 3 < h) {
|
if (ext[0] && box_bot + 3 < h) {
|
||||||
@@ -3018,7 +3106,7 @@ static void draw_side(tui_t *t) {
|
|||||||
|
|
||||||
if (h >= 4) {
|
if (h >= 4) {
|
||||||
draw_ansi_at(s, h - 3, x, mw, t->where, CP_SIDE_MUTED, 0, 0);
|
draw_ansi_at(s, h - 3, x, mw, t->where, CP_SIDE_MUTED, 0, 0);
|
||||||
snprintf(line, sizeof(line), "CST %s", CST_VERSION);
|
snprintf(line, sizeof(line), "Hermit %s", CST_VERSION);
|
||||||
draw_ansi_at(s, h - 2, x, mw, line, CP_SIDE_MUTED, 0, 0);
|
draw_ansi_at(s, h - 2, x, mw, line, CP_SIDE_MUTED, 0, 0);
|
||||||
}
|
}
|
||||||
wrefresh(s);
|
wrefresh(s);
|
||||||
@@ -3183,9 +3271,11 @@ static void draw_topbar(tui_t *t) {
|
|||||||
if (hexish)
|
if (hexish)
|
||||||
title = "New session";
|
title = "New session";
|
||||||
}
|
}
|
||||||
/* CST mark + title + model. Role lives only in the Seat Deck. */
|
/* Hermit badge + title + model. Role lives only in the Seat Deck. */
|
||||||
int x = 1;
|
int x = 1;
|
||||||
x += draw_ansi_at(w, 0, x, 3, "CST", CP_TOPBAR_ACCENT, 1, 0);
|
draw_hermit_badge(w, 0, x, t);
|
||||||
|
x += 4;
|
||||||
|
x += draw_ansi_at(w, 0, x, 6, "Hermit", CP_TOPBAR_ACCENT, 1, 0);
|
||||||
x += draw_ansi_at(w, 0, x, 1, " ", CP_TOPBAR_MUTED, 0, 0);
|
x += draw_ansi_at(w, 0, x, 1, " ", CP_TOPBAR_MUTED, 0, 0);
|
||||||
int model_room = 0;
|
int model_room = 0;
|
||||||
char mid[160] = "";
|
char mid[160] = "";
|
||||||
|
|||||||