diff --git a/README.md b/README.md index 5a72392..bd5d555 100644 --- a/README.md +++ b/README.md @@ -37,12 +37,21 @@ Telegram **手机遥控器** for DeepSeek Harness:附着本机正在跑的 Web irm https://raw.githubusercontent.com/hi-wenw/dsh-telegram-channel/master/scripts/install.ps1 | iex ``` +> `install.ps1` 是引导脚本(兼容 `irm | iex`),会再下载并运行真正的菜单 `manage.ps1`。 +> 不要直接 `irm .../manage.ps1 | iex`(带 `param` 会报错)。 + **CMD 一键:** ```bat powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://raw.githubusercontent.com/hi-wenw/dsh-telegram-channel/master/scripts/install.ps1 | iex" ``` +备用(先下载再执行): + +```bat +powershell -NoProfile -ExecutionPolicy Bypass -Command "iwr -UseBasicParsing https://raw.githubusercontent.com/hi-wenw/dsh-telegram-channel/master/scripts/manage.ps1 -OutFile $env:TEMP\dsh-tg.ps1; & $env:TEMP\dsh-tg.ps1" +``` + 启动后用**数字**选择: ``` diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 3e97852..dfc6108 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -1,380 +1,28 @@ #Requires -Version 5.1 -<# -.SYNOPSIS - dsh-telegram-channel manager menu: install / start / stop / status. +# Bootstrap for dsh-telegram-channel manager. +# Safe for: irm .../install.ps1 | iex +# (top-level param()/CmdletBinding cannot be used with Invoke-Expression) -.EXAMPLE - irm https://raw.githubusercontent.com/hi-wenw/dsh-telegram-channel/master/scripts/install.ps1 | iex - -.EXAMPLE - .\scripts\install.ps1 -Action start - .\scripts\install.ps1 -Action install -Token '...' -UserId '123' -#> -[CmdletBinding()] -param( - [ValidateSet('', 'menu', 'install', 'start', 'stop', 'status', 'open')] - [string] $Action = '', - [string] $Token = $env:DSH_TELEGRAM_TOKEN, - [string] $UserId = $env:DSH_TELEGRAM_ALLOWED_USER_IDS, - [string] $ProfileName = 'web', - [string] $Source = 'github:hi-wenw/dsh-telegram-channel', - [int] $Port = 3080, - [switch] $Local, - [switch] $NoPersist -) - -Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' -function Write-Step([string] $Message) { - Write-Host "" - Write-Host ("==> " + $Message) -ForegroundColor Cyan -} +$manageUrl = 'https://raw.githubusercontent.com/hi-wenw/dsh-telegram-channel/master/scripts/manage.ps1' +$tmp = Join-Path $env:TEMP 'dsh-telegram-channel-manage.ps1' -function Write-Ok([string] $Message) { - Write-Host $Message -ForegroundColor Green -} - -function Write-Warn2([string] $Message) { - Write-Host $Message -ForegroundColor Yellow -} - -function Ensure-Command([string] $Name) { - if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) { - throw ("Command not found: " + $Name + ". Install DeepSeek Harness CLI (dsh) and add it to PATH.") +$local = $null +if ($PSScriptRoot) { + $candidate = Join-Path $PSScriptRoot 'manage.ps1' + if (Test-Path -LiteralPath $candidate) { + $local = $candidate } } -function Get-DshHome { - if ($env:DSH_HOME) { return $env:DSH_HOME } - return (Join-Path $env:USERPROFILE '.dsh') +if ($local) { + Write-Host ("Running local manager: " + $local) -ForegroundColor Cyan + & $local @args + exit $LASTEXITCODE } -function Get-ProfileDir { - $dir = Join-Path (Get-DshHome) ('profiles\' + $ProfileName) - if (-not (Test-Path -LiteralPath $dir)) { - throw ("Profile directory not found: " + $dir + "`nRun dsh web once first.") - } - return $dir -} - -function Set-UserEnv([string] $Name, [string] $Value) { - Set-Item -Path ("Env:" + $Name) -Value $Value - if (-not $NoPersist) { - [Environment]::SetEnvironmentVariable($Name, $Value, 'User') - } -} - -function Get-UserEnv([string] $Name) { - $v = [Environment]::GetEnvironmentVariable($Name, 'User') - if ($v) { return $v } - return [Environment]::GetEnvironmentVariable($Name, 'Process') -} - -function Ensure-AllowBuilds([string] $ProfileDir) { - $path = Join-Path $ProfileDir 'pnpm-workspace.yaml' - $blockLines = @( - 'allowBuilds:' - ' dsh-telegram-channel: true' - ) - $block = $blockLines -join "`r`n" - if (-not (Test-Path -LiteralPath $path)) { - $content = @( - 'packages:' - ' - .' - '' - 'nodeLinker: hoisted' - 'autoInstallPeers: false' - '' - $block - ) -join "`r`n" - Set-Content -LiteralPath $path -Value $content -Encoding utf8 - Write-Host ("Created " + $path + " (allowBuilds)") - return - } - - $raw = Get-Content -LiteralPath $path -Raw - if ($raw -match '(?m)^\s*dsh-telegram-channel\s*:\s*true\s*$') { - Write-Host 'allowBuilds already present, skip' - return - } - - if ($raw -match '(?m)^allowBuilds\s*:') { - if ($raw -notmatch '(?m)^allowBuilds\s*:[\s\S]*?dsh-telegram-channel\s*:') { - $raw = $raw -replace '(?m)^(allowBuilds\s*:)', ('$1' + "`r`n dsh-telegram-channel: true") - $out = ($raw.TrimEnd() + "`r`n") - Set-Content -LiteralPath $path -Value $out -Encoding utf8 - Write-Host ("Wrote allowBuilds.dsh-telegram-channel -> " + $path) - } - return - } - - Add-Content -LiteralPath $path -Value ("`r`n" + $block + "`r`n") -Encoding utf8 - Write-Host ("Appended allowBuilds -> " + $path) -} - -function Get-ListenPids([int] $ListenPort) { - $pids = @() - try { - $pids = @( - Get-NetTCPConnection -LocalPort $ListenPort -State Listen -ErrorAction SilentlyContinue | - Select-Object -ExpandProperty OwningProcess -Unique - ) - } catch { - $pids = @() - } - if ($pids.Count -eq 0) { - $lines = netstat -ano | Select-String -Pattern 'LISTENING' | Select-String -Pattern (':' + $ListenPort + '\s') - foreach ($line in $lines) { - if ($line -match '\s(\d+)\s*$') { - $pids += [int]$Matches[1] - } - } - $pids = @($pids | Select-Object -Unique) - } - return $pids -} - -function Get-DshWebPids { - $found = @() - Get-CimInstance Win32_Process -Filter "Name='node.exe'" -ErrorAction SilentlyContinue | ForEach-Object { - $cmd = [string]$_.CommandLine - if ($cmd -match 'bin\.js' -and $cmd -match '\bweb\b' -and $cmd -match 'deepseek-ai') { - $found += [int]$_.ProcessId - } elseif ($cmd -match 'dsh\\lib\\bin\.js' -and $cmd -match '\bweb\b') { - $found += [int]$_.ProcessId - } - } - return @($found | Select-Object -Unique) -} - -function Show-Status { - Write-Step ('Status (profile=' + $ProfileName + ', port=' + $Port + ')') - - $token = Get-UserEnv 'DSH_TELEGRAM_TOKEN' - $allow = Get-UserEnv 'DSH_TELEGRAM_ALLOWED_USER_IDS' - if ($token) { Write-Ok ('Token: set (length ' + $token.Length + ')') } else { Write-Warn2 'Token: NOT set (DSH_TELEGRAM_TOKEN)' } - if ($allow) { Write-Ok ('Allowlist: ' + $allow) } else { Write-Warn2 'Allowlist: NOT set (DSH_TELEGRAM_ALLOWED_USER_IDS)' } - - $profileDir = $null - try { $profileDir = Get-ProfileDir } catch { Write-Warn2 $_.Exception.Message } - if ($profileDir) { - $pkg = Join-Path $profileDir 'package.json' - if (Test-Path -LiteralPath $pkg) { - $raw = Get-Content -LiteralPath $pkg -Raw - if ($raw -match 'dsh-telegram-channel') { - Write-Ok ('Plugin: found in ' + $pkg) - } else { - Write-Warn2 'Plugin: not in package.json yet (choose 1 to install)' - } - } - } - - $listen = @(Get-ListenPids $Port) - if ($listen.Count -gt 0) { - Write-Ok ('dsh web: RUNNING http://127.0.0.1:' + $Port + ' (PID ' + ($listen -join ', ') + ')') - } else { - Write-Warn2 ('dsh web: not listening on port ' + $Port + ' (choose 2 to start)') - } -} - -function Invoke-Install { - Ensure-Command 'dsh' - - $useToken = $Token - $useUserId = $UserId - if (-not $useToken) { $useToken = Get-UserEnv 'DSH_TELEGRAM_TOKEN' } - if (-not $useUserId) { $useUserId = Get-UserEnv 'DSH_TELEGRAM_ALLOWED_USER_IDS' } - - if (-not $useToken) { - $useToken = Read-Host 'Paste BotFather Bot Token' - } - $useToken = ([string]$useToken).Trim() - if (-not $useToken) { throw 'Missing Bot Token.' } - - if (-not $useUserId) { - $useUserId = Read-Host 'Paste Telegram numeric User ID (@userinfobot)' - } - $useUserId = ([string]$useUserId).Trim() - if ($useUserId -notmatch '^[0-9]+(,[0-9]+)*$') { - throw ("User ID must be numeric (comma-separated ok), got: '" + $useUserId + "'") - } - $useUserId = ($useUserId -replace '\s+', '') - - Write-Step 'Write user environment variables' - Set-UserEnv 'DSH_TELEGRAM_TOKEN' $useToken - Set-UserEnv 'DSH_TELEGRAM_ALLOWED_USER_IDS' $useUserId - Write-Host ('DSH_TELEGRAM_TOKEN = (set, length ' + $useToken.Length + ')') - Write-Host ('DSH_TELEGRAM_ALLOWED_USER_IDS = ' + $useUserId) - if ($NoPersist) { - Write-Warn2 '(-NoPersist: not written to user-level env)' - } - - $profileDir = Get-ProfileDir - Write-Step 'Ensure pnpm allowBuilds' - Ensure-AllowBuilds $profileDir - - $installSource = $Source - if ($Local) { - $scriptRoot = $PSScriptRoot - if (-not $scriptRoot -and $MyInvocation.MyCommand.Path) { - $scriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path - } - if (-not $scriptRoot) { - throw 'Cannot use -Local with remote irm|iex. git clone first, then run local script.' - } - $installSource = (Resolve-Path (Join-Path $scriptRoot '..')).Path - } - - Write-Step ('Install plugin: dsh plugin --profile ' + $ProfileName + ' add ' + $installSource) - & dsh plugin --profile $ProfileName add $installSource - if ($LASTEXITCODE -ne 0) { - throw ('dsh plugin add failed (exit=' + $LASTEXITCODE + '). Try: dsh plugin --profile ' + $ProfileName + ' remove dsh-telegram-channel') - } - - Write-Ok 'Install complete.' - Write-Host 'Next: choose 2 to start dsh web -> open a Web chat -> phone /sessions' -} - -function Invoke-Start { - Ensure-Command 'dsh' - $listen = @(Get-ListenPids $Port) - if ($listen.Count -gt 0) { - Write-Warn2 ('Already running: http://127.0.0.1:' + $Port + ' (PID ' + ($listen -join ', ') + ')') - Write-Host 'To restart: choose 3 (stop), then 2 (start).' - return - } - - $token = Get-UserEnv 'DSH_TELEGRAM_TOKEN' - $allow = Get-UserEnv 'DSH_TELEGRAM_ALLOWED_USER_IDS' - if (-not $token) { Write-Warn2 'Warning: DSH_TELEGRAM_TOKEN not set; bot may not poll.' } - if (-not $allow) { Write-Warn2 'Warning: DSH_TELEGRAM_ALLOWED_USER_IDS not set; all users denied.' } - - Write-Step 'Start dsh web in a new window (close that window to stop)' - $lines = @( - "`$env:DSH_TELEGRAM_TOKEN = [Environment]::GetEnvironmentVariable('DSH_TELEGRAM_TOKEN','User')" - "`$env:DSH_TELEGRAM_ALLOWED_USER_IDS = [Environment]::GetEnvironmentVariable('DSH_TELEGRAM_ALLOWED_USER_IDS','User')" - "if (-not `$env:DSH_TELEGRAM_TOKEN) { `$env:DSH_TELEGRAM_TOKEN = [Environment]::GetEnvironmentVariable('DSH_TELEGRAM_TOKEN','Process') }" - "if (-not `$env:DSH_TELEGRAM_ALLOWED_USER_IDS) { `$env:DSH_TELEGRAM_ALLOWED_USER_IDS = [Environment]::GetEnvironmentVariable('DSH_TELEGRAM_ALLOWED_USER_IDS','Process') }" - "Write-Host 'dsh-telegram-channel: starting dsh web ...' -ForegroundColor Cyan" - "Write-Host ('Token length: ' + (`$env:DSH_TELEGRAM_TOKEN + '').Length)" - "Write-Host ('Allow IDs: ' + `$env:DSH_TELEGRAM_ALLOWED_USER_IDS)" - "dsh web" - ) - $psCommand = $lines -join '; ' - Start-Process -FilePath 'powershell.exe' -ArgumentList @( - '-NoExit', - '-NoProfile', - '-ExecutionPolicy', 'Bypass', - '-Command', $psCommand - ) | Out-Null - - Write-Host 'New window opened. Waiting for port...' - $ok = $false - for ($i = 0; $i -lt 40; $i++) { - Start-Sleep -Milliseconds 500 - if ((Get-ListenPids $Port).Count -gt 0) { - $ok = $true - break - } - } - if ($ok) { - Write-Ok ('Started: http://127.0.0.1:' + $Port) - } else { - Write-Warn2 'Port not listening yet. Check the new window for errors.' - } -} - -function Invoke-Stop { - Write-Step ('Stop dsh web (port ' + $Port + ')') - $pids = @() - $pids += Get-ListenPids $Port - $pids += Get-DshWebPids - $pids = @($pids | Where-Object { $_ -gt 0 } | Select-Object -Unique) - - if ($pids.Count -eq 0) { - Write-Warn2 'No running dsh web found.' - return - } - - foreach ($procId in $pids) { - try { - $p = Get-Process -Id $procId -ErrorAction SilentlyContinue - $name = if ($p) { $p.ProcessName } else { '?' } - Write-Host ('Kill PID ' + $procId + ' (' + $name + ')') - Stop-Process -Id $procId -Force -ErrorAction Stop - } catch { - Write-Warn2 ('Cannot kill PID ' + $procId + ': ' + $_.Exception.Message) - } - } - - Start-Sleep -Seconds 1 - $left = @(Get-ListenPids $Port) - if ($left.Count -eq 0) { - Write-Ok 'Stopped.' - } else { - Write-Warn2 ('Port still in use: PID ' + ($left -join ', ')) - } -} - -function Invoke-Open { - $url = ('http://127.0.0.1:' + $Port) - $listen = @(Get-ListenPids $Port) - if ($listen.Count -eq 0) { - Write-Warn2 'Service not running. Choose 2 to start first.' - return - } - Start-Process $url - Write-Ok ('Opened ' + $url) -} - -function Show-Menu { - Write-Host '' - Write-Host '========================================' -ForegroundColor Green - Write-Host ' dsh-telegram-channel manager' -ForegroundColor Green - Write-Host '========================================' -ForegroundColor Green - Write-Host (' profile = ' + $ProfileName + ' port = ' + $Port) - Write-Host '' - Write-Host ' 1) Install / reinstall plugin (Token + allowlist)' - Write-Host ' 2) Start dsh web (new window)' - Write-Host ' 3) Stop dsh web' - Write-Host ' 4) Status' - Write-Host ' 5) Open browser' - Write-Host ' 0) Exit' - Write-Host '' -} - -function Invoke-MenuLoop { - while ($true) { - Show-Menu - $choice = Read-Host 'Enter number' - switch ($choice.Trim()) { - '1' { try { Invoke-Install } catch { Write-Host $_.Exception.Message -ForegroundColor Red } } - '2' { try { Invoke-Start } catch { Write-Host $_.Exception.Message -ForegroundColor Red } } - '3' { try { Invoke-Stop } catch { Write-Host $_.Exception.Message -ForegroundColor Red } } - '4' { try { Show-Status } catch { Write-Host $_.Exception.Message -ForegroundColor Red } } - '5' { try { Invoke-Open } catch { Write-Host $_.Exception.Message -ForegroundColor Red } } - '0' { Write-Host 'Bye.'; return } - '' { continue } - default { Write-Warn2 ('Invalid option: ' + $choice) } - } - } -} - -if (-not $Action) { - if ($PSBoundParameters.ContainsKey('Token') -or $PSBoundParameters.ContainsKey('UserId') -or $Local) { - $Action = 'install' - } else { - $Action = 'menu' - } -} - -switch ($Action) { - 'menu' { Invoke-MenuLoop } - 'install' { Invoke-Install } - 'start' { Invoke-Start } - 'stop' { Invoke-Stop } - 'status' { Show-Status } - 'open' { Invoke-Open } - default { throw ('Unknown Action: ' + $Action) } -} +Write-Host 'Downloading manager script...' -ForegroundColor Cyan +Invoke-WebRequest -UseBasicParsing -Uri $manageUrl -OutFile $tmp +& $tmp @args +exit $LASTEXITCODE diff --git a/scripts/manage.ps1 b/scripts/manage.ps1 new file mode 100644 index 0000000..3e97852 --- /dev/null +++ b/scripts/manage.ps1 @@ -0,0 +1,380 @@ +#Requires -Version 5.1 +<# +.SYNOPSIS + dsh-telegram-channel manager menu: install / start / stop / status. + +.EXAMPLE + irm https://raw.githubusercontent.com/hi-wenw/dsh-telegram-channel/master/scripts/install.ps1 | iex + +.EXAMPLE + .\scripts\install.ps1 -Action start + .\scripts\install.ps1 -Action install -Token '...' -UserId '123' +#> +[CmdletBinding()] +param( + [ValidateSet('', 'menu', 'install', 'start', 'stop', 'status', 'open')] + [string] $Action = '', + [string] $Token = $env:DSH_TELEGRAM_TOKEN, + [string] $UserId = $env:DSH_TELEGRAM_ALLOWED_USER_IDS, + [string] $ProfileName = 'web', + [string] $Source = 'github:hi-wenw/dsh-telegram-channel', + [int] $Port = 3080, + [switch] $Local, + [switch] $NoPersist +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +function Write-Step([string] $Message) { + Write-Host "" + Write-Host ("==> " + $Message) -ForegroundColor Cyan +} + +function Write-Ok([string] $Message) { + Write-Host $Message -ForegroundColor Green +} + +function Write-Warn2([string] $Message) { + Write-Host $Message -ForegroundColor Yellow +} + +function Ensure-Command([string] $Name) { + if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) { + throw ("Command not found: " + $Name + ". Install DeepSeek Harness CLI (dsh) and add it to PATH.") + } +} + +function Get-DshHome { + if ($env:DSH_HOME) { return $env:DSH_HOME } + return (Join-Path $env:USERPROFILE '.dsh') +} + +function Get-ProfileDir { + $dir = Join-Path (Get-DshHome) ('profiles\' + $ProfileName) + if (-not (Test-Path -LiteralPath $dir)) { + throw ("Profile directory not found: " + $dir + "`nRun dsh web once first.") + } + return $dir +} + +function Set-UserEnv([string] $Name, [string] $Value) { + Set-Item -Path ("Env:" + $Name) -Value $Value + if (-not $NoPersist) { + [Environment]::SetEnvironmentVariable($Name, $Value, 'User') + } +} + +function Get-UserEnv([string] $Name) { + $v = [Environment]::GetEnvironmentVariable($Name, 'User') + if ($v) { return $v } + return [Environment]::GetEnvironmentVariable($Name, 'Process') +} + +function Ensure-AllowBuilds([string] $ProfileDir) { + $path = Join-Path $ProfileDir 'pnpm-workspace.yaml' + $blockLines = @( + 'allowBuilds:' + ' dsh-telegram-channel: true' + ) + $block = $blockLines -join "`r`n" + if (-not (Test-Path -LiteralPath $path)) { + $content = @( + 'packages:' + ' - .' + '' + 'nodeLinker: hoisted' + 'autoInstallPeers: false' + '' + $block + ) -join "`r`n" + Set-Content -LiteralPath $path -Value $content -Encoding utf8 + Write-Host ("Created " + $path + " (allowBuilds)") + return + } + + $raw = Get-Content -LiteralPath $path -Raw + if ($raw -match '(?m)^\s*dsh-telegram-channel\s*:\s*true\s*$') { + Write-Host 'allowBuilds already present, skip' + return + } + + if ($raw -match '(?m)^allowBuilds\s*:') { + if ($raw -notmatch '(?m)^allowBuilds\s*:[\s\S]*?dsh-telegram-channel\s*:') { + $raw = $raw -replace '(?m)^(allowBuilds\s*:)', ('$1' + "`r`n dsh-telegram-channel: true") + $out = ($raw.TrimEnd() + "`r`n") + Set-Content -LiteralPath $path -Value $out -Encoding utf8 + Write-Host ("Wrote allowBuilds.dsh-telegram-channel -> " + $path) + } + return + } + + Add-Content -LiteralPath $path -Value ("`r`n" + $block + "`r`n") -Encoding utf8 + Write-Host ("Appended allowBuilds -> " + $path) +} + +function Get-ListenPids([int] $ListenPort) { + $pids = @() + try { + $pids = @( + Get-NetTCPConnection -LocalPort $ListenPort -State Listen -ErrorAction SilentlyContinue | + Select-Object -ExpandProperty OwningProcess -Unique + ) + } catch { + $pids = @() + } + if ($pids.Count -eq 0) { + $lines = netstat -ano | Select-String -Pattern 'LISTENING' | Select-String -Pattern (':' + $ListenPort + '\s') + foreach ($line in $lines) { + if ($line -match '\s(\d+)\s*$') { + $pids += [int]$Matches[1] + } + } + $pids = @($pids | Select-Object -Unique) + } + return $pids +} + +function Get-DshWebPids { + $found = @() + Get-CimInstance Win32_Process -Filter "Name='node.exe'" -ErrorAction SilentlyContinue | ForEach-Object { + $cmd = [string]$_.CommandLine + if ($cmd -match 'bin\.js' -and $cmd -match '\bweb\b' -and $cmd -match 'deepseek-ai') { + $found += [int]$_.ProcessId + } elseif ($cmd -match 'dsh\\lib\\bin\.js' -and $cmd -match '\bweb\b') { + $found += [int]$_.ProcessId + } + } + return @($found | Select-Object -Unique) +} + +function Show-Status { + Write-Step ('Status (profile=' + $ProfileName + ', port=' + $Port + ')') + + $token = Get-UserEnv 'DSH_TELEGRAM_TOKEN' + $allow = Get-UserEnv 'DSH_TELEGRAM_ALLOWED_USER_IDS' + if ($token) { Write-Ok ('Token: set (length ' + $token.Length + ')') } else { Write-Warn2 'Token: NOT set (DSH_TELEGRAM_TOKEN)' } + if ($allow) { Write-Ok ('Allowlist: ' + $allow) } else { Write-Warn2 'Allowlist: NOT set (DSH_TELEGRAM_ALLOWED_USER_IDS)' } + + $profileDir = $null + try { $profileDir = Get-ProfileDir } catch { Write-Warn2 $_.Exception.Message } + if ($profileDir) { + $pkg = Join-Path $profileDir 'package.json' + if (Test-Path -LiteralPath $pkg) { + $raw = Get-Content -LiteralPath $pkg -Raw + if ($raw -match 'dsh-telegram-channel') { + Write-Ok ('Plugin: found in ' + $pkg) + } else { + Write-Warn2 'Plugin: not in package.json yet (choose 1 to install)' + } + } + } + + $listen = @(Get-ListenPids $Port) + if ($listen.Count -gt 0) { + Write-Ok ('dsh web: RUNNING http://127.0.0.1:' + $Port + ' (PID ' + ($listen -join ', ') + ')') + } else { + Write-Warn2 ('dsh web: not listening on port ' + $Port + ' (choose 2 to start)') + } +} + +function Invoke-Install { + Ensure-Command 'dsh' + + $useToken = $Token + $useUserId = $UserId + if (-not $useToken) { $useToken = Get-UserEnv 'DSH_TELEGRAM_TOKEN' } + if (-not $useUserId) { $useUserId = Get-UserEnv 'DSH_TELEGRAM_ALLOWED_USER_IDS' } + + if (-not $useToken) { + $useToken = Read-Host 'Paste BotFather Bot Token' + } + $useToken = ([string]$useToken).Trim() + if (-not $useToken) { throw 'Missing Bot Token.' } + + if (-not $useUserId) { + $useUserId = Read-Host 'Paste Telegram numeric User ID (@userinfobot)' + } + $useUserId = ([string]$useUserId).Trim() + if ($useUserId -notmatch '^[0-9]+(,[0-9]+)*$') { + throw ("User ID must be numeric (comma-separated ok), got: '" + $useUserId + "'") + } + $useUserId = ($useUserId -replace '\s+', '') + + Write-Step 'Write user environment variables' + Set-UserEnv 'DSH_TELEGRAM_TOKEN' $useToken + Set-UserEnv 'DSH_TELEGRAM_ALLOWED_USER_IDS' $useUserId + Write-Host ('DSH_TELEGRAM_TOKEN = (set, length ' + $useToken.Length + ')') + Write-Host ('DSH_TELEGRAM_ALLOWED_USER_IDS = ' + $useUserId) + if ($NoPersist) { + Write-Warn2 '(-NoPersist: not written to user-level env)' + } + + $profileDir = Get-ProfileDir + Write-Step 'Ensure pnpm allowBuilds' + Ensure-AllowBuilds $profileDir + + $installSource = $Source + if ($Local) { + $scriptRoot = $PSScriptRoot + if (-not $scriptRoot -and $MyInvocation.MyCommand.Path) { + $scriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path + } + if (-not $scriptRoot) { + throw 'Cannot use -Local with remote irm|iex. git clone first, then run local script.' + } + $installSource = (Resolve-Path (Join-Path $scriptRoot '..')).Path + } + + Write-Step ('Install plugin: dsh plugin --profile ' + $ProfileName + ' add ' + $installSource) + & dsh plugin --profile $ProfileName add $installSource + if ($LASTEXITCODE -ne 0) { + throw ('dsh plugin add failed (exit=' + $LASTEXITCODE + '). Try: dsh plugin --profile ' + $ProfileName + ' remove dsh-telegram-channel') + } + + Write-Ok 'Install complete.' + Write-Host 'Next: choose 2 to start dsh web -> open a Web chat -> phone /sessions' +} + +function Invoke-Start { + Ensure-Command 'dsh' + $listen = @(Get-ListenPids $Port) + if ($listen.Count -gt 0) { + Write-Warn2 ('Already running: http://127.0.0.1:' + $Port + ' (PID ' + ($listen -join ', ') + ')') + Write-Host 'To restart: choose 3 (stop), then 2 (start).' + return + } + + $token = Get-UserEnv 'DSH_TELEGRAM_TOKEN' + $allow = Get-UserEnv 'DSH_TELEGRAM_ALLOWED_USER_IDS' + if (-not $token) { Write-Warn2 'Warning: DSH_TELEGRAM_TOKEN not set; bot may not poll.' } + if (-not $allow) { Write-Warn2 'Warning: DSH_TELEGRAM_ALLOWED_USER_IDS not set; all users denied.' } + + Write-Step 'Start dsh web in a new window (close that window to stop)' + $lines = @( + "`$env:DSH_TELEGRAM_TOKEN = [Environment]::GetEnvironmentVariable('DSH_TELEGRAM_TOKEN','User')" + "`$env:DSH_TELEGRAM_ALLOWED_USER_IDS = [Environment]::GetEnvironmentVariable('DSH_TELEGRAM_ALLOWED_USER_IDS','User')" + "if (-not `$env:DSH_TELEGRAM_TOKEN) { `$env:DSH_TELEGRAM_TOKEN = [Environment]::GetEnvironmentVariable('DSH_TELEGRAM_TOKEN','Process') }" + "if (-not `$env:DSH_TELEGRAM_ALLOWED_USER_IDS) { `$env:DSH_TELEGRAM_ALLOWED_USER_IDS = [Environment]::GetEnvironmentVariable('DSH_TELEGRAM_ALLOWED_USER_IDS','Process') }" + "Write-Host 'dsh-telegram-channel: starting dsh web ...' -ForegroundColor Cyan" + "Write-Host ('Token length: ' + (`$env:DSH_TELEGRAM_TOKEN + '').Length)" + "Write-Host ('Allow IDs: ' + `$env:DSH_TELEGRAM_ALLOWED_USER_IDS)" + "dsh web" + ) + $psCommand = $lines -join '; ' + Start-Process -FilePath 'powershell.exe' -ArgumentList @( + '-NoExit', + '-NoProfile', + '-ExecutionPolicy', 'Bypass', + '-Command', $psCommand + ) | Out-Null + + Write-Host 'New window opened. Waiting for port...' + $ok = $false + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Milliseconds 500 + if ((Get-ListenPids $Port).Count -gt 0) { + $ok = $true + break + } + } + if ($ok) { + Write-Ok ('Started: http://127.0.0.1:' + $Port) + } else { + Write-Warn2 'Port not listening yet. Check the new window for errors.' + } +} + +function Invoke-Stop { + Write-Step ('Stop dsh web (port ' + $Port + ')') + $pids = @() + $pids += Get-ListenPids $Port + $pids += Get-DshWebPids + $pids = @($pids | Where-Object { $_ -gt 0 } | Select-Object -Unique) + + if ($pids.Count -eq 0) { + Write-Warn2 'No running dsh web found.' + return + } + + foreach ($procId in $pids) { + try { + $p = Get-Process -Id $procId -ErrorAction SilentlyContinue + $name = if ($p) { $p.ProcessName } else { '?' } + Write-Host ('Kill PID ' + $procId + ' (' + $name + ')') + Stop-Process -Id $procId -Force -ErrorAction Stop + } catch { + Write-Warn2 ('Cannot kill PID ' + $procId + ': ' + $_.Exception.Message) + } + } + + Start-Sleep -Seconds 1 + $left = @(Get-ListenPids $Port) + if ($left.Count -eq 0) { + Write-Ok 'Stopped.' + } else { + Write-Warn2 ('Port still in use: PID ' + ($left -join ', ')) + } +} + +function Invoke-Open { + $url = ('http://127.0.0.1:' + $Port) + $listen = @(Get-ListenPids $Port) + if ($listen.Count -eq 0) { + Write-Warn2 'Service not running. Choose 2 to start first.' + return + } + Start-Process $url + Write-Ok ('Opened ' + $url) +} + +function Show-Menu { + Write-Host '' + Write-Host '========================================' -ForegroundColor Green + Write-Host ' dsh-telegram-channel manager' -ForegroundColor Green + Write-Host '========================================' -ForegroundColor Green + Write-Host (' profile = ' + $ProfileName + ' port = ' + $Port) + Write-Host '' + Write-Host ' 1) Install / reinstall plugin (Token + allowlist)' + Write-Host ' 2) Start dsh web (new window)' + Write-Host ' 3) Stop dsh web' + Write-Host ' 4) Status' + Write-Host ' 5) Open browser' + Write-Host ' 0) Exit' + Write-Host '' +} + +function Invoke-MenuLoop { + while ($true) { + Show-Menu + $choice = Read-Host 'Enter number' + switch ($choice.Trim()) { + '1' { try { Invoke-Install } catch { Write-Host $_.Exception.Message -ForegroundColor Red } } + '2' { try { Invoke-Start } catch { Write-Host $_.Exception.Message -ForegroundColor Red } } + '3' { try { Invoke-Stop } catch { Write-Host $_.Exception.Message -ForegroundColor Red } } + '4' { try { Show-Status } catch { Write-Host $_.Exception.Message -ForegroundColor Red } } + '5' { try { Invoke-Open } catch { Write-Host $_.Exception.Message -ForegroundColor Red } } + '0' { Write-Host 'Bye.'; return } + '' { continue } + default { Write-Warn2 ('Invalid option: ' + $choice) } + } + } +} + +if (-not $Action) { + if ($PSBoundParameters.ContainsKey('Token') -or $PSBoundParameters.ContainsKey('UserId') -or $Local) { + $Action = 'install' + } else { + $Action = 'menu' + } +} + +switch ($Action) { + 'menu' { Invoke-MenuLoop } + 'install' { Invoke-Install } + 'start' { Invoke-Start } + 'stop' { Invoke-Stop } + 'status' { Show-Status } + 'open' { Invoke-Open } + default { throw ('Unknown Action: ' + $Action) } +}