From 4ac16aa82fa695a64b005636547b65dc96f128bc Mon Sep 17 00:00:00 2001 From: owo <17620121969@163.com> Date: Fri, 14 Aug 2026 17:53:22 +0800 Subject: [PATCH] feat: turn installer into number menu with start/stop/status Open install.ps1/sh to choose install, start dsh web in a new window, stop listeners, status, or open the browser. Co-authored-by: Cursor --- README.md | 81 +++++---- scripts/install.ps1 | 401 ++++++++++++++++++++++++++++++++++---------- scripts/install.sh | 193 +++++++++++++-------- 3 files changed, 478 insertions(+), 197 deletions(-) diff --git a/README.md b/README.md index b302118..5a72392 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Telegram **手机遥控器** for DeepSeek Harness:附着本机正在跑的 Web --- -### 一键安装(推荐) +### 一键管理菜单(推荐) **先准备两样东西:** @@ -31,66 +31,56 @@ Telegram **手机遥控器** for DeepSeek Harness:附着本机正在跑的 Web #### Windows -> 下面命令请在 **PowerShell** 里执行(开始菜单搜 “Windows PowerShell” / “终端”)。 -> 若窗口标题是 **命令提示符 / CMD**,先输入 `powershell` 回车,再贴命令。 - -**方式 A — 远程一键(交互输入 Token / User ID):** +> 请在 **PowerShell** 执行。若当前是 **CMD**,用下面「CMD 一键」那行。 ```powershell irm https://raw.githubusercontent.com/hi-wenw/dsh-telegram-channel/master/scripts/install.ps1 | iex ``` -等价写法(更不易混淆): - -```powershell -Invoke-RestMethod https://raw.githubusercontent.com/hi-wenw/dsh-telegram-channel/master/scripts/install.ps1 | Invoke-Expression -``` - -**CMD 用户一键进入并执行:** +**CMD 一键:** ```bat powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://raw.githubusercontent.com/hi-wenw/dsh-telegram-channel/master/scripts/install.ps1 | iex" ``` -**方式 B — 带参数(不交互):** +启动后用**数字**选择: -```powershell -git clone https://github.com/hi-wenw/dsh-telegram-channel.git -cd dsh-telegram-channel -powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\install.ps1 -Token '这里粘贴Token' -UserId '这里粘贴数字ID' +``` +1) 安装 / 重装插件(写入 Token + 白名单) +2) 启动 dsh web(新窗口) +3) 停止 dsh web +4) 查看状态 +5) 打开浏览器 +0) 退出 ``` -脚本会自动: +也可直接指定动作(不进菜单): -1. 写入用户环境变量 `DSH_TELEGRAM_TOKEN`、`DSH_TELEGRAM_ALLOWED_USER_IDS` -2. 给 profile 补上 `allowBuilds`(防止 pnpm 拦截) -3. 执行 `dsh plugin --profile web add github:hi-wenw/dsh-telegram-channel` +```powershell +.\scripts\install.ps1 -Action install -Token '...' -UserId '123456789' +.\scripts\install.ps1 -Action start +.\scripts\install.ps1 -Action stop +.\scripts\install.ps1 -Action status +``` -**不会**再 `insert` 同名插件行(避免 `duplicate loader entry id`)。 +安装时脚本会:写环境变量、补 `allowBuilds`、执行 `dsh plugin add`(**不会**再 insert 同名 id)。 #### macOS / Linux ```bash -export DSH_TELEGRAM_TOKEN='你的Token' -export DSH_TELEGRAM_ALLOWED_USER_IDS='你的数字ID' curl -fsSL https://raw.githubusercontent.com/hi-wenw/dsh-telegram-channel/master/scripts/install.sh | bash +# 同样出现数字菜单;或: +# ./scripts/install.sh install --token '...' --user-id '...' +# ./scripts/install.sh start|stop|status ``` -或克隆后:`./scripts/install.sh --token '...' --user-id '...'` - --- -### 安装后只用三步 +### 手机怎么用 -```powershell -# 1. 关掉旧终端,新开一个,然后: -dsh web - -# 2. 浏览器打开一个对话(保持运行) - -# 3. 手机对 Bot: -# /start → /sessions → 点选 → 正常聊天 -``` +1. 菜单选 **2** 启动 `dsh web`(或自己运行 `dsh web`) +2. 浏览器打开一个对话并保持运行 +3. 手机对 Bot:`/start` → `/sessions` → 点选 → 聊天 输入框旁的 **/** 菜单应有:`start` `sessions` `status` `unbind` `help`。 @@ -185,25 +175,30 @@ npm run build Telegram **mobile remote** for **live** DeepSeek Harness Web agents. Desktop/Web is the source of truth; the phone **attaches** (no parallel hidden agent). -### One-click install (Windows) +### One-click manager (Windows) + +Run in **PowerShell** (not CMD). Opens a number menu: install / start / stop / status / open browser. ```powershell irm https://raw.githubusercontent.com/hi-wenw/dsh-telegram-channel/master/scripts/install.ps1 | iex ``` -Or with params after clone: +CMD: -```powershell -.\scripts\install.ps1 -Token '...' -UserId '123456789' +```bat +powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://raw.githubusercontent.com/hi-wenw/dsh-telegram-channel/master/scripts/install.ps1 | iex" ``` -The script sets user env vars, ensures `allowBuilds`, and runs `dsh plugin add`. Then: +Direct actions: ```powershell -dsh web -# open a Web chat → phone: /sessions → bind +.\scripts\install.ps1 -Action start +.\scripts\install.ps1 -Action stop +.\scripts\install.ps1 -Action install -Token '...' -UserId '123456789' ``` +The script sets user env vars, ensures `allowBuilds`, and runs `dsh plugin add`. After **start**, open a Web chat → phone: `/sessions` → bind. + ### Unix ```bash diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 455e95e..3e97852 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -1,31 +1,24 @@ -#Requires -Version 5.1 +#Requires -Version 5.1 <# .SYNOPSIS - One-click install for dsh-telegram-channel (DeepSeek Harness Telegram remote). - -.DESCRIPTION - 1) Writes user env: DSH_TELEGRAM_TOKEN + DSH_TELEGRAM_ALLOWED_USER_IDS - 2) Ensures allowBuilds for the plugin (belt-and-suspenders) - 3) Runs: dsh plugin --profile add - Does NOT edit cordis.patch.yml (avoids duplicate loader entry). + dsh-telegram-channel manager menu: install / start / stop / status. .EXAMPLE - # Interactive (prompts for token + user id) irm https://raw.githubusercontent.com/hi-wenw/dsh-telegram-channel/master/scripts/install.ps1 | iex .EXAMPLE - .\scripts\install.ps1 -Token '123:AA...' -UserId '7057906059' - -.EXAMPLE - # Install from this cloned repo instead of GitHub - .\scripts\install.ps1 -Token '...' -UserId '...' -Local + .\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 ) @@ -35,121 +28,353 @@ $ErrorActionPreference = 'Stop' function Write-Step([string] $Message) { Write-Host "" - Write-Host "==> $Message" -ForegroundColor Cyan + 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 "找不到命令 '$Name'。请先安装并确保 DeepSeek Harness CLI(dsh)在 PATH 中。" + 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 + 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' - $block = @' -allowBuilds: - dsh-telegram-channel: true -'@ + $blockLines = @( + 'allowBuilds:' + ' dsh-telegram-channel: true' + ) + $block = $blockLines -join "`r`n" if (-not (Test-Path -LiteralPath $path)) { - $content = @" -packages: - - . - -nodeLinker: hoisted -autoInstallPeers: false - -$block -"@ + $content = @( + 'packages:' + ' - .' + '' + 'nodeLinker: hoisted' + 'autoInstallPeers: false' + '' + $block + ) -join "`r`n" Set-Content -LiteralPath $path -Value $content -Encoding utf8 - Write-Host "已创建 $path (allowBuilds)" + 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 已存在,跳过" + 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" + $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 "已写入 allowBuilds.dsh-telegram-channel → $path" + Write-Host ("Wrote allowBuilds.dsh-telegram-channel -> " + $path) } return } - Add-Content -LiteralPath $path -Value "`r`n$block`r`n" -Encoding utf8 - Write-Host "已追加 allowBuilds → $path" + Add-Content -LiteralPath $path -Value ("`r`n" + $block + "`r`n") -Encoding utf8 + Write-Host ("Appended allowBuilds -> " + $path) } -Write-Host "dsh-telegram-channel installer" -ForegroundColor Green -Write-Host "Profile: $ProfileName" - -Ensure-Command 'dsh' - -if (-not $Token) { - $Token = Read-Host '粘贴 BotFather 的 Bot Token(不要发到公开群)' -} -$Token = ([string]$Token).Trim() -if (-not $Token) { throw '缺少 Bot Token。' } - -if (-not $UserId) { - $UserId = Read-Host '粘贴你的 Telegram 数字 User ID(@userinfobot)' -} -$UserId = ([string]$UserId).Trim() -if ($UserId -notmatch '^\d+(?:\s*,\s*\d+)*$') { - throw "User ID 必须是数字(多个用逗号分隔),收到: '$UserId'" -} -$UserId = ($UserId -replace '\s+', '') - -Write-Step '写入用户环境变量(当前进程 + 用户级永久)' -Set-UserEnv 'DSH_TELEGRAM_TOKEN' $Token -Set-UserEnv 'DSH_TELEGRAM_ALLOWED_USER_IDS' $UserId -Write-Host "DSH_TELEGRAM_TOKEN = (已设置,长度 $($Token.Length))" -Write-Host "DSH_TELEGRAM_ALLOWED_USER_IDS = $UserId" -if ($NoPersist) { - Write-Host '(-NoPersist:未写入用户级环境变量)' -ForegroundColor Yellow -} else { - Write-Host '已写入用户级环境变量。请关闭旧终端后再开新的。' -ForegroundColor Yellow +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 } -$dshHome = if ($env:DSH_HOME) { $env:DSH_HOME } else { Join-Path $env:USERPROFILE '.dsh' } -$profileDir = Join-Path $dshHome "profiles\$ProfileName" -if (-not (Test-Path -LiteralPath $profileDir)) { - throw "找不到 profile 目录: $profileDir (请先成功跑过一次 dsh web)" +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) } -Write-Step '确保 pnpm allowBuilds(防止 git 依赖 prepare 被拦)' -Ensure-AllowBuilds $profileDir +function Show-Status { + Write-Step ('Status (profile=' + $ProfileName + ', port=' + $Port + ')') -if ($Local) { - $scriptRoot = if ($PSScriptRoot) { $PSScriptRoot } else { Split-Path -Parent $MyInvocation.MyCommand.Path } - $repoRoot = Resolve-Path (Join-Path $scriptRoot '..') - $Source = $repoRoot.Path + $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)') + } } -Write-Step "安装插件: dsh plugin --profile $ProfileName add $Source" -& dsh plugin --profile $ProfileName add $Source -if ($LASTEXITCODE -ne 0) { - throw "dsh plugin add 失败(exit=$LASTEXITCODE)。可尝试: dsh plugin --profile $ProfileName remove dsh-telegram-channel 后再跑本脚本。" +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' } -Write-Host "" -Write-Host '安装完成。接下来:' -ForegroundColor Green -Write-Host ' 1. 关掉所有旧终端,新开 PowerShell' -Write-Host ' 2. 确认代理(若需要访问 Telegram)已开启' -Write-Host ' 3. 运行: dsh web' -Write-Host ' 4. 浏览器打开一个对话' -Write-Host ' 5. 手机对 Bot 发 /start → /sessions → 点选附着' -Write-Host "" -Write-Host '命令菜单应出现: /start /sessions /status /unbind /help' +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) } +} diff --git a/scripts/install.sh b/scripts/install.sh index cd826dc..e311932 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1,19 +1,16 @@ #!/usr/bin/env bash -# One-click-ish install for dsh-telegram-channel (macOS / Linux). -# Usage: -# export DSH_TELEGRAM_TOKEN='123:AA...' -# export DSH_TELEGRAM_ALLOWED_USER_IDS='7057906059' -# curl -fsSL https://raw.githubusercontent.com/hi-wenw/dsh-telegram-channel/master/scripts/install.sh | bash -# Or: -# ./scripts/install.sh --token '...' --user-id '...' +# dsh-telegram-channel manager (install / start / stop / status) +# curl -fsSL https://raw.githubusercontent.com/hi-wenw/dsh-telegram-channel/master/scripts/install.sh | bash set -euo pipefail PROFILE_NAME="${DSH_PROFILE:-web}" SOURCE="${DSH_TELEGRAM_SOURCE:-github:hi-wenw/dsh-telegram-channel}" TOKEN="${DSH_TELEGRAM_TOKEN:-}" USER_ID="${DSH_TELEGRAM_ALLOWED_USER_IDS:-}" +PORT="${DSH_WEB_PORT:-3080}" LOCAL=0 NO_PERSIST=0 +ACTION="menu" while [[ $# -gt 0 ]]; do case "$1" in @@ -21,35 +18,29 @@ while [[ $# -gt 0 ]]; do --user-id) USER_ID="${2:-}"; shift 2 ;; --profile) PROFILE_NAME="${2:-}"; shift 2 ;; --source) SOURCE="${2:-}"; shift 2 ;; + --port) PORT="${2:-}"; shift 2 ;; --local) LOCAL=1; shift ;; --no-persist) NO_PERSIST=1; shift ;; + menu|install|start|stop|status) ACTION="$1"; shift ;; -h|--help) - sed -n '2,12p' "$0" + cat <&2; exit 1 ;; esac done +need_dsh() { + command -v dsh >/dev/null 2>&1 || { echo "dsh not found in PATH" >&2; exit 1; } +} -if ! command -v dsh >/dev/null 2>&1; then - echo "dsh not found in PATH" >&2 - exit 1 -fi - -if [[ -z "$TOKEN" ]]; then - read -r -p "Bot Token: " TOKEN -fi -if [[ -z "$USER_ID" ]]; then - read -r -p "Telegram numeric User ID: " USER_ID -fi -USER_ID="$(echo "$USER_ID" | tr -d '[:space:]')" -if ! [[ "$USER_ID" =~ ^[0-9]+(,[0-9]+)*$ ]]; then - echo "User ID must be numeric (comma-separated ok)" >&2 - exit 1 -fi - -export DSH_TELEGRAM_TOKEN="$TOKEN" -export DSH_TELEGRAM_ALLOWED_USER_IDS="$USER_ID" +profile_dir() { + local home="${DSH_HOME:-$HOME/.dsh}" + local dir="$home/profiles/$PROFILE_NAME" + [[ -d "$dir" ]] || { echo "Profile dir missing: $dir (run dsh web once first)" >&2; exit 1; } + echo "$dir" +} persist_env() { local name="$1" value="$2" @@ -57,7 +48,6 @@ persist_env() { for f in "$HOME/.bashrc" "$HOME/.zshrc"; do [[ -f "$f" ]] || continue if grep -q "^export ${name}=" "$f" 2>/dev/null; then - # portable-ish replace tmp="$(mktemp)" grep -v "^export ${name}=" "$f" >"$tmp" || true echo "$line" >>"$tmp" @@ -68,22 +58,10 @@ persist_env() { done } -if [[ "$NO_PERSIST" -eq 0 ]]; then - persist_env DSH_TELEGRAM_TOKEN "$TOKEN" - persist_env DSH_TELEGRAM_ALLOWED_USER_IDS "$USER_ID" - echo "Wrote exports to ~/.bashrc and/or ~/.zshrc (if present)." -fi - -DSH_HOME="${DSH_HOME:-$HOME/.dsh}" -PROFILE_DIR="$DSH_HOME/profiles/$PROFILE_NAME" -if [[ ! -d "$PROFILE_DIR" ]]; then - echo "Profile dir missing: $PROFILE_DIR (run dsh web once first)" >&2 - exit 1 -fi - -WS="$PROFILE_DIR/pnpm-workspace.yaml" -if [[ ! -f "$WS" ]]; then - cat >"$WS" <<'EOF' +ensure_allow_builds() { + local ws="$1/pnpm-workspace.yaml" + if [[ ! -f "$ws" ]]; then + cat >"$ws" <<'EOF' packages: - . @@ -93,32 +71,115 @@ autoInstallPeers: false allowBuilds: dsh-telegram-channel: true EOF -elif ! grep -q 'dsh-telegram-channel:' "$WS"; then - if grep -q '^allowBuilds:' "$WS"; then - awk ' - BEGIN{done=0} - /^allowBuilds:/{print; print " dsh-telegram-channel: true"; done=1; next} - {print} - END{if(!done){print ""; print "allowBuilds:"; print " dsh-telegram-channel: true"}} - ' "$WS" >"$WS.tmp" && mv "$WS.tmp" "$WS" - else - printf '\nallowBuilds:\n dsh-telegram-channel: true\n' >>"$WS" + elif ! grep -q 'dsh-telegram-channel:' "$ws"; then + if grep -q '^allowBuilds:' "$ws"; then + awk 'BEGIN{done=0} /^allowBuilds:/{print; print " dsh-telegram-channel: true"; done=1; next} {print} END{if(!done){print ""; print "allowBuilds:"; print " dsh-telegram-channel: true"}}' "$ws" >"$ws.tmp" && mv "$ws.tmp" "$ws" + else + printf '\nallowBuilds:\n dsh-telegram-channel: true\n' >>"$ws" + fi fi -fi +} -if [[ "$LOCAL" -eq 1 ]]; then - SOURCE="$(cd "$(dirname "$0")/.." && pwd)" -fi +listen_pids() { + if command -v lsof >/dev/null 2>&1; then + lsof -tiTCP:"$PORT" -sTCP:LISTEN 2>/dev/null || true + elif command -v ss >/dev/null 2>&1; then + ss -ltnp 2>/dev/null | awk -v p=":$PORT" '$4 ~ p {print}' | sed -n 's/.*pid=\([0-9]*\).*/\1/p' | sort -u + fi +} -echo "==> dsh plugin --profile $PROFILE_NAME add $SOURCE" -dsh plugin --profile "$PROFILE_NAME" add "$SOURCE" +do_status() { + echo "==> status profile=$PROFILE_NAME port=$PORT" + if [[ -n "${DSH_TELEGRAM_TOKEN:-}" ]]; then echo "Token: set (len=${#DSH_TELEGRAM_TOKEN})"; else echo "Token: missing"; fi + if [[ -n "${DSH_TELEGRAM_ALLOWED_USER_IDS:-}" ]]; then echo "Allow: $DSH_TELEGRAM_ALLOWED_USER_IDS"; else echo "Allow: missing"; fi + local pids + pids="$(listen_pids | tr '\n' ' ')" + if [[ -n "${pids// /}" ]]; then echo "dsh web: running PIDs $pids http://127.0.0.1:$PORT"; else echo "dsh web: not listening"; fi +} -cat <&2; exit 1; } + export DSH_TELEGRAM_TOKEN="$TOKEN" + export DSH_TELEGRAM_ALLOWED_USER_IDS="$USER_ID" + if [[ "$NO_PERSIST" -eq 0 ]]; then + persist_env DSH_TELEGRAM_TOKEN "$TOKEN" + persist_env DSH_TELEGRAM_ALLOWED_USER_IDS "$USER_ID" + fi + local dir + dir="$(profile_dir)" + ensure_allow_builds "$dir" + if [[ "$LOCAL" -eq 1 ]]; then + SOURCE="$(cd "$(dirname "$0")/.." && pwd)" + fi + echo "==> dsh plugin --profile $PROFILE_NAME add $SOURCE" + dsh plugin --profile "$PROFILE_NAME" add "$SOURCE" + echo "Install done. Use menu option 2 / ./install.sh start" +} -Install done. Next: - 1. Open a new shell (so env vars load) - 2. Run: dsh web - 3. Open a Web conversation - 4. On phone: /start → /sessions → bind +do_start() { + need_dsh + if [[ -n "$(listen_pids)" ]]; then + echo "Already running on :$PORT" + return 0 + fi + echo "==> starting dsh web in background (log: /tmp/dsh-web.log)" + nohup dsh web >/tmp/dsh-web.log 2>&1 & + echo $! >/tmp/dsh-web.pid + sleep 1 + do_status +} + +do_stop() { + echo "==> stop" + local pids + pids="$(listen_pids)" + if [[ -z "$pids" && -f /tmp/dsh-web.pid ]]; then + pids="$(cat /tmp/dsh-web.pid 2>/dev/null || true)" + fi + if [[ -z "$pids" ]]; then + echo "Not running" + return 0 + fi + echo "$pids" | xargs -r kill 2>/dev/null || echo "$pids" | xargs kill 2>/dev/null || true + rm -f /tmp/dsh-web.pid + echo "Stopped" +} + +do_menu() { + while true; do + cat <&2; exit 1 ;; +esac