From 42ebee50b74c7f22dd23a9429abf73b86d20a7b1 Mon Sep 17 00:00:00 2001 From: chuan Date: Wed, 24 Jun 2026 01:00:32 +0800 Subject: [PATCH 1/3] =?UTF-8?q?build:=20AOT=20=E5=8F=91=E5=B8=83=E5=90=8E?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=20UPX=20nrv9=20=E5=8E=8B=E7=BC=A9=EF=BC=8840?= =?UTF-8?q?MB=E2=86=9214MB=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - build.bat:publish 后清理产物 + upx -9 自动压缩,支持 SKIP_UPX 跳过、缺 upx 仅警告 - csproj:新增框架依赖单文件发布路径(非 AOT 时),.Static 包改为仅 AOT 引用 --- Notify/Notify.csproj | 22 ++++++++++++++++++++-- scripts/build.bat | 21 ++++++++++++++++++--- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/Notify/Notify.csproj b/Notify/Notify.csproj index 9570b4c..054b717 100644 --- a/Notify/Notify.csproj +++ b/Notify/Notify.csproj @@ -16,7 +16,16 @@ true - + + + true + false + true + false + + + win-x64 true @@ -50,8 +59,9 @@ CoreUtils.SkiaSharp.Static 含 skia + libHarfBuzzSharp 的 .lib,CoreUtils.ANGLE.Static 含 ANGLE 两包各自的 .targets 会在 PublishAot 时自动追加 NativeLibrary,这里只补 DirectPInvoke 与系统 lib 版本对应:Avalonia 12 → SkiaSharp 3.119 + 仅 AOT 需要;框架依赖单文件用 Avalonia 自带的动态 Skia 原生库 --> - + @@ -79,4 +89,12 @@ + + + + <_SingleFileJunk Include="$(PublishDir)*.pdb" /> + + + + diff --git a/scripts/build.bat b/scripts/build.bat index 6040b19..fa029ea 100644 --- a/scripts/build.bat +++ b/scripts/build.bat @@ -9,7 +9,7 @@ if exist "%VSWHERE%" ( for /f "usebackq tokens=*" %%i in (`"%VSWHERE%" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSPATH=%%i" ) if defined VSPATH if exist "%VSPATH%\VC\Auxiliary\Build\vcvars64.bat" ( - echo === 配置 MSVC 环境: %VSPATH% === + echo === Configure MSVC env: %VSPATH% === call "%VSPATH%\VC\Auxiliary\Build\vcvars64.bat" >nul ) @@ -17,10 +17,25 @@ echo === NativeAOT publish (win-x64) -^> bin\notify.exe === dotnet publish Notify -c Release -r win-x64 -p:PublishAot=true -o bin if errorlevel 1 ( echo. - echo *** 发布失败。若提示找不到 link.exe,请从 "Developer Command Prompt for VS" 运行本脚本 *** + echo *** publish failed. If link.exe not found, run from "Developer Command Prompt for VS" *** exit /b 1 ) +rem 清理发布目录残留:只留 notify.exe(AOT 下 -o 拷贝时序使 csproj 内清理不可靠,这里统一处理) +del /q bin\*.pdb bin\*.dll bin\*.lib >nul 2>&1 + +rem 自动 UPX 压缩(NRV -9:大小/解压速度最佳平衡,约 40MB->14MB,启动仅多 ~100ms) +rem 跳过压缩:先 set SKIP_UPX=1 再运行本脚本 +if "%SKIP_UPX%"=="1" goto after_upx +where upx >nul 2>&1 +if errorlevel 1 goto no_upx +echo === UPX compress (nrv9) === +upx -9 bin\notify.exe +goto after_upx +:no_upx +echo *** WARNING: upx not found on PATH, skip compression (output is uncompressed exe) *** +:after_upx + echo. -echo === 完成: %CD%\bin\notify.exe === +echo === Done: %CD%\bin\notify.exe === endlocal From 5fad621130852144877cf01634b92dc33bd4264f Mon Sep 17 00:00:00 2001 From: chuan Date: Wed, 24 Jun 2026 01:18:41 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=E9=80=9A=E7=9F=A5=E7=A8=8B=E5=BA=8F?= =?UTF-8?q?=E6=94=B9=E5=BC=82=E6=AD=A5=E5=90=8E=E5=8F=B0=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=EF=BC=8Chook=20=E4=B8=8D=E5=86=8D=E5=9B=A0=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E6=85=A2=E9=98=BB=E5=A1=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - exe 缺失时派生脱离 hook 的后台下载进程,立即返回并通过 systemMessage 汇报进度,不受 30s 超时影响 - curl -C - 断点续传,失败保留 .partial 下次接着下 - 原子 mkdir 锁防止多次触发重复下载;进程被硬杀的陈旧锁靠 .partial 闲置自愈 - systemMessage 用 ASCII(中文经 cmd 会被破坏成非法 JSON) --- scripts/notify.cmd | 82 +++++++++++++++++++++++++++------------------- scripts/notify.sh | 55 +++++++++++++++++-------------- 2 files changed, 79 insertions(+), 58 deletions(-) diff --git a/scripts/notify.cmd b/scripts/notify.cmd index ad1d06a..246f394 100644 --- a/scripts/notify.cmd +++ b/scripts/notify.cmd @@ -6,40 +6,56 @@ rem ============================================================ setlocal set "BIN=%~dp0..\bin" set "EXE=%BIN%\notify.exe" +set "PART=%BIN%\notify.exe.partial" set "LOCK=%BIN%\notify.download.lock" -rem only bootstrap on first run; the common path runs the exe directly (keeps piped stdin intact) -if not exist "%EXE%" call :bootstrap +rem hidden self-reinvocation: detached background downloader (see :downloader) +if "%~1"=="__download" goto downloader -if exist "%EXE%" "%EXE%" %* -endlocal -exit /b - -:bootstrap -if not exist "%BIN%" mkdir "%BIN%" 2>nul -set "TMP=%BIN%\notify.exe.%RANDOM%.tmp" -rem mkdir is atomic; success = this process downloads, failure = someone else is downloading -mkdir "%LOCK%" 2>nul -if errorlevel 1 goto :waitdl -rem double-check in case it just finished -if exist "%EXE%" ( rmdir "%LOCK%" 2>nul & exit /b ) -rem download to temp then atomic rename, so no half-written exe is ever seen -curl -fsSL "%DOWNLOAD_URL%" -o "%TMP%" -if errorlevel 1 ( del "%TMP%" 2>nul & rmdir "%LOCK%" 2>nul & exit /b ) -move /y "%TMP%" "%EXE%" >nul -rmdir "%LOCK%" 2>nul -exit /b - -:waitdl -rem did not get the lock; wait for the exe to appear (up to ~60s) -set /a _w=0 -:waitloop -if exist "%EXE%" exit /b -if %_w% geq 120 ( - rem timed out; a killed download may have left a stale lock, clear it for next time - rmdir "%LOCK%" 2>nul - exit /b +rem common path: exe present -> run directly (keeps piped stdin intact) +if exist "%EXE%" ( + "%EXE%" %* + endlocal & exit /b ) -ping -n 2 127.0.0.1 >nul -set /a _w+=1 -goto :waitloop + +rem ---- exe missing: never block the hook ---- +rem kick off the download once (atomic mkdir lock); a detached worker survives the +rem hook timeout. then report progress to Claude and return immediately. +if not exist "%BIN%" mkdir "%BIN%" 2>nul + +rem self-heal: if a previous worker was hard-killed (shutdown/crash) it may leave a +rem stale lock that blocks all retries. reclaim it so -C - can resume the .partial. +if exist "%LOCK%" call :reclaim + +rem atomic lock: only the first hook spawns the worker; others fall through and just +rem report progress -> no duplicate downloads even when hooks fire concurrently. +mkdir "%LOCK%" 2>nul && start "" /b "%~f0" __download nul 2>&1 + +rem downloaded size so far (from the .partial file), shown as X.X MB +set "DLBYTES=0" +if exist "%PART%" for %%A in ("%PART%") do set "DLBYTES=%%~zA" +set /a DLKB=DLBYTES/1024 +set /a DLMB=DLKB/1024 +set /a DLF=(DLKB*10/1024)%%10 + +rem exit 0 so Claude parses this JSON; systemMessage is shown to the user, the +rem notification itself is skipped this time (suppressOutput hides raw stdout) +echo {"suppressOutput":true,"systemMessage":"[Claude Code Notify] notifier not ready, downloading in background: %DLMB%.%DLF% MB / ~14 MB done. Works automatically once finished; this notification is skipped."} +endlocal & exit /b 0 + +:downloader +rem detached worker: resume-capable download (-C -), atomic install, always free lock. +rem if curl fails (slow/flaky net), the .partial is kept and the next hook resumes it. +curl -fsSL -C - "%DOWNLOAD_URL%" -o "%PART%" +if not errorlevel 1 move /y "%PART%" "%EXE%" >nul 2>&1 +rmdir "%LOCK%" 2>nul +endlocal & exit /b + +:reclaim +rem no .partial yet => worker died before downloading anything; reclaim immediately +if not exist "%PART%" ( rmdir "%LOCK%" 2>nul & goto :eof ) +rem .partial idle for >120s => worker is dead (a live curl writes continuously); reclaim +for /f "delims=" %%T in ('powershell -nop -c "[int]((Get-Date)-(Get-Item '%PART%').LastWriteTime).TotalSeconds" 2^>nul') do set "IDLE=%%T" +if not defined IDLE goto :eof +if %IDLE% geq 120 rmdir "%LOCK%" 2>nul +goto :eof diff --git a/scripts/notify.sh b/scripts/notify.sh index 123ce6a..fd71037 100644 --- a/scripts/notify.sh +++ b/scripts/notify.sh @@ -7,34 +7,39 @@ DOWNLOAD_URL="https://git.pchuan.top/cc-tools/notify/releases/download/v1.0.0/no DIR="$(cd "$(dirname "$0")" && pwd)" BIN="$DIR/../bin" EXE="$BIN/notify.exe" +PART="$BIN/notify.exe.partial" LOCK="$BIN/notify.download.lock" -if [ ! -f "$EXE" ]; then - mkdir -p "$BIN" 2>/dev/null - # mkdir 是原子操作,用作锁:成功=本进程负责下载,失败=已有进程在下 - if mkdir "$LOCK" 2>/dev/null; then - # 双重检查,避免刚好别人下完 - if [ ! -f "$EXE" ]; then - TMP="$BIN/notify.exe.$$.tmp" - if curl -fsSL "$DOWNLOAD_URL" -o "$TMP"; then - mv -f "$TMP" "$EXE" # 原子改名,避免半截 exe - chmod +x "$EXE" 2>/dev/null - else - rm -f "$TMP" - fi - fi +# 常规路径:exe 已就绪 -> 直接转发参数与 stdin(保持管道完整) +if [ -f "$EXE" ]; then + exec "$EXE" "$@" +fi + +# ---- exe 缺失:绝不阻塞 hook ---- +mkdir -p "$BIN" 2>/dev/null + +# 自愈:上次下载进程被硬杀(关机/崩溃)可能留下陈旧锁,挡住所有重试。 +# .partial 不存在(没真正开始)或 >2 分钟没增长(进程已死)则回收锁,让 -C - 续传。 +if [ -d "$LOCK" ]; then + if [ ! -f "$PART" ]; then + rmdir "$LOCK" 2>/dev/null + elif [ -n "$(find "$PART" -mmin +2 2>/dev/null)" ]; then rmdir "$LOCK" 2>/dev/null - else - # 没抢到锁:等 exe 出现(最多约 60 秒) - i=0 - while [ ! -f "$EXE" ] && [ "$i" -lt 120 ]; do - sleep 0.5 - i=$((i + 1)) - done - # 超时仍没下好:可能上次下载被杀留下陈旧锁,清掉让下次重下 - [ ! -f "$EXE" ] && rmdir "$LOCK" 2>/dev/null fi fi -# 转发全部参数与 stdin 给真正的 exe -[ -f "$EXE" ] && exec "$EXE" "$@" +# 原子锁:只有第一个 hook 派生唯一的后台下载进程;并发/后续 hook 抢锁失败 +# -> 不重复下载,只汇报进度。下载脱离 hook 后台进行,不受 30s 超时影响。 +if mkdir "$LOCK" 2>/dev/null; then + # 断点续传 -C -;失败保留 .partial 供下次续传;无论成败都释放锁 + nohup sh -c "curl -fsSL -C - '$DOWNLOAD_URL' -o '$PART' && mv -f '$PART' '$EXE' && chmod +x '$EXE'; rmdir '$LOCK' 2>/dev/null" >/dev/null 2>&1 & +fi + +# 已下载大小(取 .partial 字节数),汇报给 Claude +DLBYTES=0 +[ -f "$PART" ] && DLBYTES=$(wc -c < "$PART" 2>/dev/null | tr -d ' ') +DLMB=$(awk "BEGIN{printf \"%.1f\", $DLBYTES/1048576}") + +# exit 0 让 Claude 解析此 JSON;systemMessage 展示给用户,本次通知跳过 +printf '{"suppressOutput":true,"systemMessage":"[Claude Code Notify] notifier not ready, downloading in background: %s MB / ~14 MB done. Works automatically once finished; this notification is skipped."}\n' "$DLMB" +exit 0 From 6438b7713375b2fb30ff4e6955ac8bbbdd4d84fd Mon Sep 17 00:00:00 2001 From: chuan Date: Wed, 24 Jun 2026 01:29:27 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=E5=90=8E=E5=8F=B0=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E6=94=B9=E7=94=A8=20Start-Process=EF=BC=8C=E9=81=BF=E5=85=8D?= =?UTF-8?q?=E5=AD=90=E8=BF=9B=E7=A8=8B=E7=BB=A7=E6=89=BF=E7=AE=A1=E9=81=93?= =?UTF-8?q?=E5=8F=A5=E6=9F=84=E9=98=BB=E5=A1=9E=20hook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit start /b 派生的进程会继承 Claude 传给 hook 的 stdout 管道句柄,curl 攥着不放 导致 Claude 读不到 EOF、直到下载完才放行(hook 被卡住)。改用 powershell Start-Process 启动下载,它默认不继承父进程句柄,hook 立即返回、下载后台继续。 --- scripts/notify.cmd | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/notify.cmd b/scripts/notify.cmd index 246f394..ef58437 100644 --- a/scripts/notify.cmd +++ b/scripts/notify.cmd @@ -29,7 +29,12 @@ if exist "%LOCK%" call :reclaim rem atomic lock: only the first hook spawns the worker; others fall through and just rem report progress -> no duplicate downloads even when hooks fire concurrently. -mkdir "%LOCK%" 2>nul && start "" /b "%~f0" __download nul 2>&1 +rem +rem must use Start-Process (not `start /b`): a child started by cmd inherits the +rem hook's stdout pipe handle, so Claude won't see EOF until the download ends -> +rem the hook would block. Start-Process spawns WITHOUT inheriting handles, so the +rem hook returns immediately while curl keeps running detached. +mkdir "%LOCK%" 2>nul && powershell -nop -w hidden -c "Start-Process -WindowStyle Hidden -FilePath '%~f0' -ArgumentList '__download'" >nul 2>&1 rem downloaded size so far (from the .partial file), shown as X.X MB set "DLBYTES=0"