From 03847bdb296565f3deb62bdbc199a361f55e9847 Mon Sep 17 00:00:00 2001 From: moranzhi Date: Wed, 30 Sep 2026 12:00:19 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=AF=E5=8A=A8=E7=BD=91=E9=A1=B5=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E6=97=B6=E4=B8=8D=E5=86=8D=E7=9B=91=E8=A7=86=E6=96=87?= =?UTF-8?q?=E4=BB=B6=EF=BC=8C=E5=B9=B6=E5=8A=A0=E4=B8=8A=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E5=8F=B0=E8=84=9A=E6=9C=AC=E7=94=A8=E6=9D=A5=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E3=80=81=E5=90=AF=E5=81=9C=E5=92=8C=E7=9C=8B=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- console-icon.ico | Bin 0 -> 6207 bytes console-icon.svg | 9 +++ console.bat | 162 +++++++++++++++++++++++++++++++++++++++++++++ console.ps1 | 166 +++++++++++++++++++++++++++++++++++++++++++++++ start.bat | 4 +- 5 files changed, 339 insertions(+), 2 deletions(-) create mode 100644 console-icon.ico create mode 100644 console-icon.svg create mode 100644 console.bat create mode 100644 console.ps1 diff --git a/console-icon.ico b/console-icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..98bad0fd4c1650325b87b21430f28308399efc99 GIT binary patch literal 6207 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 V<$<;hKTzN|pJ!WK?fkDu{{xTQBq0C* literal 0 HcmV?d00001 diff --git a/console-icon.svg b/console-icon.svg new file mode 100644 index 0000000..92ee654 --- /dev/null +++ b/console-icon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/console.bat b/console.bat new file mode 100644 index 0000000..26d5004 --- /dev/null +++ b/console.bat @@ -0,0 +1,162 @@ +@echo off +chcp 65001 >nul +setlocal EnableExtensions EnableDelayedExpansion +cd /d "%~dp0" +title Writing Agent +set "PS=powershell -NoProfile -ExecutionPolicy Bypass -File" +set "CTL=%~dp0console.ps1" + +call :ensure +if errorlevel 1 ( + echo. + echo 启动失败。 + pause + goto :menu +) +start "" "http://127.0.0.1:23337/" + +:menu +cls +echo. +echo ======================================== +echo Writing Agent 本机 +echo http://localhost:23337 +echo 不走隧道,也不在服务机上跑。 +echo 不监视文件。改完代码后输入 update。 +echo ======================================== +echo. +echo start 启动服务并打开页面 +echo stop 停止服务 +echo restart 重启服务 +echo update 用当前代码重启服务 +echo open 打开页面 +echo status 端口和 git 状态 +echo pull git pull --ff-only +echo push git push +echo log 最近日志 +echo help +echo exit 退出控制台,服务继续运行 +echo. +set "LINE=" +set /p "LINE=指令: " +if not defined LINE goto :menu +call :dispatch "%LINE%" +if "%ERRORLEVEL%"=="2" goto :eof +goto :menu + +:dispatch +set "RAW=%~1" +set "CHK=%RAW:&=%" +if not "%CHK%"=="%RAW%" goto :bad +set "CHK=%RAW:|=%" +if not "%CHK%"=="%RAW%" goto :bad +set "CHK=%RAW:>=%" +if not "%CHK%"=="%RAW%" goto :bad +set "CHK=%RAW:<=%" +if not "%CHK%"=="%RAW%" goto :bad +set "CMD=" +for /f "tokens=1" %%A in ("%RAW%") do set "CMD=%%A" +if /i "%CMD%"=="exit" exit /b 2 +if /i "%CMD%"=="quit" exit /b 2 +if /i "%CMD%"=="help" goto :show +if /i "%CMD%"=="start" goto :do_start +if /i "%CMD%"=="stop" goto :do_stop +if /i "%CMD%"=="restart" goto :do_update +if /i "%CMD%"=="update" goto :do_update +if /i "%CMD%"=="open" goto :do_open +if /i "%CMD%"=="status" goto :do_status +if /i "%CMD%"=="pull" goto :do_pull +if /i "%CMD%"=="push" goto :do_push +if /i "%CMD%"=="log" goto :do_log +goto :bad + +:show +echo. +echo update 重启进程以加载当前代码,不监视文件。 +echo pull 快进拉取,不自动重启。拉取后需要 update。 +echo push 推送当前分支,不提交、不强制推送。 +echo exit 只关这个窗口。 +echo. +pause +exit /b 0 + +:do_start +echo. +%PS% "%CTL%" -Action start +if errorlevel 1 goto :held +start "" "http://127.0.0.1:23337/" +echo. +pause +exit /b 0 + +:do_stop +echo. +%PS% "%CTL%" -Action stop +echo. +pause +exit /b 0 + +:do_update +echo. +%PS% "%CTL%" -Action restart +if errorlevel 1 goto :held +echo 已用当前代码重启,没有开启文件监视。 +echo. +pause +exit /b 0 + +:do_open +start "" "http://127.0.0.1:23337/" +exit /b 0 + +:do_status +echo. +%PS% "%CTL%" -Action status +echo. +pause +exit /b 0 + +:do_pull +echo. +git pull --ff-only +echo. +echo 代码已更新到磁盘后,输入 update 才会重启服务。 +echo. +pause +exit /b 0 + +:do_push +echo. +git push +echo. +pause +exit /b 0 + +:do_log +echo. +%PS% "%CTL%" -Action log +echo. +pause +exit /b 0 + +:ensure +set "MODE=" +for /f "delims=" %%A in ('%PS% "%CTL%" -Action query') do set "MODE=%%A" +if /i "!MODE!"=="web" exit /b 0 +echo. +echo 正在启动服务(不监视文件)... +%PS% "%CTL%" -Action start +if errorlevel 1 exit /b 1 +exit /b 0 + +:bad +echo. +echo 不认识这条指令。输入 help 查看。 +echo. +pause +exit /b 0 + +:held +echo. +pause +exit /b 0 diff --git a/console.ps1 b/console.ps1 new file mode 100644 index 0000000..f71e58b --- /dev/null +++ b/console.ps1 @@ -0,0 +1,166 @@ +# Start and stop the local writing-agent web server. File watch stays off. +param( + [Parameter(Mandatory = $true)] + [ValidateSet("query", "start", "stop", "restart", "status", "log")] + [string]$Action +) + +$ErrorActionPreference = "Stop" +$ProgressPreference = "SilentlyContinue" +$Root = Split-Path -Parent $MyInvocation.MyCommand.Path +$Port = 23337 +$Log = Join-Path $env:USERPROFILE ".writing-agent\logs\runtime.log" + +function Get-ListenerPid { + $lines = netstat -ano -p tcp | Select-String ":$Port\s" | Select-String "LISTENING" + foreach ($line in $lines) { + if ("$line" -match '\sLISTENING\s+(\d+)\s*$') { + return [int]$Matches[1] + } + } + return $null +} + +function Get-ProcessById([int]$ProcessId) { + Get-CimInstance Win32_Process -Filter "ProcessId=$ProcessId" -ErrorAction SilentlyContinue +} + +function Test-ServerCommand([string]$CommandLine) { + if (-not $CommandLine) { return $false } + if ($CommandLine -match 'Cursor|cursor-agent') { return $false } + return $CommandLine -match 'src[\\/]+server[\\/]+web-server|web:watch|tsx(\.cmd)?\s+watch|npm(\.cmd)?\s+run\s+web' +} + +function Get-ChainText([int]$ProcessId) { + $parts = New-Object System.Collections.Generic.List[string] + $current = $ProcessId + for ($i = 0; $i -lt 10; $i++) { + $proc = Get-ProcessById $current + if (-not $proc) { break } + $cmd = [string]$proc.CommandLine + $parts.Add($cmd) + if ($cmd -match 'Cursor|cursor-agent') { break } + $parent = [int]$proc.ParentProcessId + if ($parent -le 0 -or $parent -eq $current) { break } + $current = $parent + } + return ($parts -join "`n") +} + +function Get-ProtectedPids { + $protected = New-Object 'System.Collections.Generic.HashSet[int]' + $current = $PID + for ($i = 0; $i -lt 8; $i++) { + if ($current -le 0 -or -not $protected.Add($current)) { break } + $proc = Get-ProcessById $current + if (-not $proc) { break } + $parent = [int]$proc.ParentProcessId + if ($parent -le 0 -or $parent -eq $current) { break } + $current = $parent + } + return $protected +} + +function Test-ForeignConsole([string]$CommandLine) { + if (-not $CommandLine) { return $false } + return $CommandLine -match 'tunnel\.ps1|ops-menu|srv\.bat|console\.bat' +} + +function Get-StopRoot([int]$ProcessId, $Protected) { + $current = $ProcessId + $root = $ProcessId + $matched = $false + for ($i = 0; $i -lt 10; $i++) { + if ($Protected.Contains($current)) { break } + $proc = Get-ProcessById $current + if (-not $proc) { break } + $cmd = [string]$proc.CommandLine + if ($cmd -match 'Cursor|cursor-agent') { break } + if (Test-ForeignConsole $cmd) { break } + if (Test-ServerCommand $cmd) { + $root = $current + $matched = $true + } + $parent = [int]$proc.ParentProcessId + if ($parent -le 0 -or $parent -eq $current) { break } + $current = $parent + } + if (-not $matched) { return $null } + if ($Protected.Contains($root)) { return $null } + return $root +} + +function Stop-Server { + $listener = Get-ListenerPid + if (-not $listener) { + Write-Output "down" + return + } + $root = Get-StopRoot $listener (Get-ProtectedPids) + if (-not $root) { + throw "Port $Port is in use by another process (pid $listener)." + } + & taskkill.exe /F /T /PID $root | Out-Null + if ($LASTEXITCODE -ne 0) { throw "taskkill failed ($LASTEXITCODE)" } + Start-Sleep -Seconds 1 + if (Get-ListenerPid) { throw "Port $Port is still in use." } + Write-Output "stopped" +} + +function Get-Mode { + $listener = Get-ListenerPid + if (-not $listener) { return "down" } + $chain = Get-ChainText $listener + if ($chain -notmatch 'web-server|npm(\.cmd)?\s+run\s+web') { return "other" } + if ($chain -match 'web:watch|tsx(\.cmd)?\s+watch') { return "watch" } + return "web" +} + +function Start-Server { + $mode = Get-Mode + if ($mode -eq "other") { + throw "Port $Port is in use by another process (pid $(Get-ListenerPid))." + } + if ($mode -eq "watch") { Stop-Server | Out-Null } + if ($mode -eq "web") { + Write-Output "already" + return + } + $npm = "D:\tool\node\npm.cmd" + $found = Get-Command npm.cmd -ErrorAction SilentlyContinue + if ($found) { $npm = $found.Source } + if (-not (Test-Path $npm)) { throw "npm.cmd not found" } + Start-Process -FilePath "cmd.exe" -ArgumentList "/c", "`"$npm`" run web" -WorkingDirectory $Root -WindowStyle Hidden | Out-Null + $deadline = (Get-Date).AddSeconds(40) + do { + Start-Sleep -Seconds 1 + $listener = Get-ListenerPid + if ($listener) { + $chain = Get-ChainText $listener + if ($chain -notmatch 'ssh\.exe') { + Write-Output "started" + return + } + } + } while ((Get-Date) -lt $deadline) + throw "Server did not listen on $Port." +} + +switch ($Action) { + "query" { Write-Output (Get-Mode) } + "start" { Start-Server } + "stop" { Stop-Server } + "restart" { + Stop-Server | Out-Null + Start-Server + } + "status" { + Write-Output ("mode=" + (Get-Mode)) + Push-Location $Root + try { git status -sb } finally { Pop-Location } + } + "log" { + if (-not (Test-Path $Log)) { Write-Output "no log: $Log"; break } + Get-Content -Path $Log -Tail 40 + } +} diff --git a/start.bat b/start.bat index e90679c..0a08d14 100644 --- a/start.bat +++ b/start.bat @@ -41,7 +41,7 @@ if exist ".env" ( ) echo [信息] 启动服务 http://localhost:%PORT% -echo [信息] 改 src 或 skills 后后端会自动重启 +echo [信息] 不监视文件。改完代码后关掉本窗口再启动,或用 console.bat 输入 update echo [信息] 后端日志在本窗口,文件在 %USERPROFILE%\.writing-agent\logs\runtime.log echo [信息] 浏览器按 F12 打开开发者工具 echo [信息] 桌面窗口请用 start-app.bat @@ -50,6 +50,6 @@ echo. start "" "http://localhost:%PORT%" -call npm run web:watch +call npm run web pause