diff --git a/README.md b/README.md
index 462f5eb8..a5a2fcd7 100644
--- a/README.md
+++ b/README.md
@@ -64,7 +64,7 @@ Every time GenericAgent solves a new task, it automatically crystallizes the exe
| :--- | :--- |
| 🧬 **Self-Evolving** | Automatically crystallizes each task into a Skill. Capabilities grow with every use, forming your personal skill tree. |
| 🪶 **Minimal Architecture** | ~3K lines of core code. Agent Loop is ~100 lines. No complex dependencies, zero deployment overhead. |
-| ⚡ **Strong Execution** | Injects into a real browser (preserving login sessions). 9 atomic tools take direct control of the system. |
+| ⚡ **Strong Execution** | **TMWebdriver** injects into a real browser (preserving login sessions). 9 atomic tools take direct control of the system. |
| 🔌 **High Compatibility** | Supports Claude / Gemini / Kimi / MiniMax and other major models. Cross-platform. |
| 💰 **Token Efficient** | <30K context window — a fraction of the 200K–1M other agents consume. Less noise, fewer hallucinations, higher success rate, lower cost. |
@@ -129,34 +129,37 @@ curl -fsSL https://raw.githubusercontent.com/lsdefine/GenericAgent/refs/heads/ma
### For Humans
-#### Method 1 — One-line install *(recommended)*
-
-This installs GenericAgent with an isolated Python environment and Git, then downloads a ready-to-run package.
-
-**Windows PowerShell**
-
-```powershell
-powershell -ExecutionPolicy Bypass -c "$env:GLOBAL=1; irm http://fudankw.cn:9000/files/ga_install.ps1 | iex"
-```
-
-**Linux / macOS**
-
-```bash
-GLOBAL=1 bash -c "$(curl -fsSL http://fudankw.cn:9000/files/ga_install.sh)"
-```
-
-After installation, launch the desktop app:
-
-- **Windows** — double-click `frontends/GenericAgent.exe`
-- **Linux / macOS** — run `python launch.pyw` from the install directory
-
-#### Method 2 — Python install *(for developers)*
+#### Method 1 — Clone & install *(recommended)*
```bash
git clone https://github.com/lsdefine/GenericAgent.git && cd GenericAgent
uv venv && uv pip install -e ".[ui]"
cp mykey_template_en.py mykey.py # fill in your LLM API key
-python launch.pyw
+```
+
+The agent core depends on just **one** library — `requests`. The other four lightweight packages (`beautifulsoup4`, `bottle`, `simple-websocket-server`, `aiohttp`) power TMWebdriver's local server. No Playwright, no LangChain, no browser binaries to download.
+
+Then launch:
+
+```bash
+python frontends/tui_v3.py # Terminal UI (recommended)
+python launch.pyw # Streamlit web UI
+```
+
+#### Method 2 — One-line installer *(convenience)*
+
+Sets up a self-contained directory with an isolated Python environment, Git, and a ready-to-run package. The script is in [`assets/`](assets/) if you'd like to read it first.
+
+**Windows PowerShell**
+
+```powershell
+powershell -ExecutionPolicy Bypass -c "$env:GLOBAL=1; irm https://raw.githubusercontent.com/lsdefine/GenericAgent/main/assets/ga_install.ps1 | iex"
+```
+
+**Linux / macOS**
+
+```bash
+GLOBAL=1 bash -c "$(curl -fsSL https://raw.githubusercontent.com/lsdefine/GenericAgent/main/assets/ga_install.sh)"
```
> 💡 GenericAgent grows its environment **through the Agent itself** — don't pre-install everything. See [Unlocking Advanced Capabilities](#-unlocking-advanced-capabilities) below.
@@ -167,20 +170,12 @@ python launch.pyw
### Frontends
-#### Desktop App
+#### Terminal UI *(recommended)*
-For one-line installs on Windows, double-click:
-
-```text
-frontends/GenericAgent.exe
-```
-
-#### Terminal UI
-
-A lightweight, keyboard-driven interface built on [Textual](https://github.com/Textualize/textual). Supports multiple concurrent sessions and real-time streaming.
+A lightweight, scrollback-first terminal interface built on `prompt_toolkit` + `rich`. Supports multiple concurrent sessions and real-time streaming.
```bash
-python frontends/tuiapp_v2.py
+python frontends/tui_v3.py
```
@@ -188,10 +183,10 @@ python frontends/tuiapp_v2.py
TUI rendering on Windows can be flaky depending on terminal + font. Common causes:
-1. `textual` is not on the latest version — `pip install -U textual` first.
+1. `prompt_toolkit` / `rich` are not on the latest version — `pip install -U prompt_toolkit rich` first.
2. PowerShell / cmd ship with terminals that have rough Unicode + key-binding support. **Prefer Git Bash on Windows**, which is much better behaved.
3. If it still looks broken, ask GA itself to fix it:
- > *"My experience using `frontends/tuiapp_v2.py` in PowerShell / cmd / Git Bash on Windows is very poor — lots of incompatibility. Please refer to Claude Code's best practices for the Windows terminal and fix all font and rendering incompatibilities."*
+ > *"My experience using `frontends/tui_v3.py` in PowerShell / cmd / Git Bash on Windows is very poor — lots of incompatibility. Please refer to Claude Code's best practices for the Windows terminal and fix all font and rendering incompatibilities."*
@@ -362,9 +357,9 @@ Baselines across these dimensions include **Claude Code**, **OpenAI CodeX**, and
-### Browser Realness of GA Web Tools
+### Browser Realness of GA Web Tools (TMWebdriver)
-GA web tools run through a **real, persistent Chrome/Chromium session** rather than a disposable headless sandbox, preserving cookies, login state, extensions, GPU/WebGL behavior, and normal browser-session fingerprints.
+GA web tools are powered by **TMWebdriver** — a local WebSocket server plus a Chrome extension — running through a **real, persistent Chrome/Chromium session** rather than a disposable headless sandbox, preserving cookies, login state, extensions, GPU/WebGL behavior, and normal browser-session fingerprints.
| Detection Service / Signal | Vanilla Headless Automation | GA Web Tools | Notes |
| :--- | :---: | :---: | :--- |
diff --git a/TMWebDriver.py b/TMWebDriver.py
index b0e1a343..c14535a1 100644
--- a/TMWebDriver.py
+++ b/TMWebDriver.py
@@ -93,12 +93,11 @@ class TMWebDriver:
session_id = data.get('sessionId')
code = data.get('code')
timeout = float(data.get('timeout', 10.0))
- try:
- result = self.execute_js(code, timeout=timeout, session_id=session_id)
- print('[remote result]', (str(code)[:50] + ' RESULT:' +str(result)[:50]).replace('\n', ' '))
- return json.dumps({'r': result}, ensure_ascii=False)
- except Exception as e:
- return json.dumps({'r': {'error': str(e)}}, ensure_ascii=False)
+ try: result = self.execute_js(code, timeout=timeout, session_id=session_id)
+ except Exception as e: return json.dumps({'r': {'error': str(e)}}, ensure_ascii=False)
+ try: print('[remote result]', (str(code)[:50] + ' RESULT:' +str(result)[:50]).replace('\n', ' '))
+ except Exception: pass
+ return json.dumps({'r': result}, ensure_ascii=False)
return 'ok'
def run():
from wsgiref.simple_server import make_server, WSGIServer, WSGIRequestHandler
diff --git a/assets/ga_install.ps1 b/assets/ga_install.ps1
new file mode 100644
index 00000000..5d5e1950
--- /dev/null
+++ b/assets/ga_install.ps1
@@ -0,0 +1,577 @@
+#requires -version 5.1
+
+<#!
+
+GenericAgent one-click portable deployer for Windows.
+
+
+
+Modes:
+
+ Default/Mainland: download GenericAgent.zip + uv + PortableGit from user's VPS, set China PyPI mirror.
+
+ GLOBAL=1: clone GenericAgent from GitHub; uv and PortableGit also come from GitHub releases; no PyPI mirror.
+
+
+
+Portable components are installed under \.portable:
+
+ uv, Python installed by uv, PortableGit.
+
+#>
+
+param(
+
+ [string]$InstallDir = "$env:USERPROFILE\GenericAgent",
+
+ [string]$PythonVersion = "3.12",
+
+ [switch]$Force
+
+)
+
+
+
+$ErrorActionPreference = "Stop"
+
+
+
+# Make Chinese output reliable in Windows PowerShell 5.1 and redirected logs.
+
+try {
+
+ [Console]::InputEncoding = [System.Text.Encoding]::UTF8
+
+ [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
+
+ $OutputEncoding = [System.Text.Encoding]::UTF8
+
+} catch { }
+
+
+
+$RepoUrl = "https://github.com/lsdefine/GenericAgent.git"
+
+$VpsBase = "http://47.101.182.29:9000"
+
+$GaZipUrl = "$VpsBase/files/GenericAgent.zip"
+
+$UvUrl = "$VpsBase/uv/uv-x86_64-pc-windows-msvc.zip"
+
+$GitUrl = "$VpsBase/files/PortableGit-2.54.0-64-bit.7z.exe"
+
+$Deps = @("requests>=2.28", "beautifulsoup4>=4.12", "bottle>=0.12", "simple-websocket-server>=0.4", "streamlit>=1.28")
+
+$MainlandIndex = "https://pypi.tuna.tsinghua.edu.cn/simple"
+
+$GlobalMode = ($env:GLOBAL -eq "1")
+
+if ($GlobalMode) {
+ # GLOBAL=1: fetch everything from GitHub; no mainland endpoints involved.
+ $UvUrl = "https://github.com/astral-sh/uv/releases/latest/download/uv-x86_64-pc-windows-msvc.zip"
+ $GitUrl = "https://github.com/git-for-windows/git/releases/download/v2.54.0.windows.1/PortableGit-2.54.0-64-bit.7z.exe"
+}
+
+
+
+$GaDir = [IO.Path]::GetFullPath($InstallDir)
+
+$PortableRoot = Join-Path $GaDir ".portable"
+
+$Bin = Join-Path $PortableRoot "bin"
+
+$Cache = Join-Path $PortableRoot "cache"
+
+$Tools = Join-Path $PortableRoot "tools"
+
+$UvZip = Join-Path $Cache "uv-x86_64-pc-windows-msvc.zip"
+
+$GaZip = Join-Path $Cache "GenericAgent.zip"
+
+$GitExeArchive = Join-Path $Cache "PortableGit-2.54.0-64-bit.7z.exe"
+
+$UvExtract = Join-Path $Cache "uv-extract"
+
+$GaExtract = Join-Path $Cache "ga-extract"
+
+$GitDir = Join-Path $Tools "PortableGit"
+
+$UvExe = Join-Path $Bin "uv.exe"
+
+$GitExe = Join-Path $GitDir "bin\git.exe"
+
+$EnvCmd = Join-Path $GaDir "env.cmd"
+
+$EnvPs1 = Join-Path $GaDir "env.ps1"
+
+
+
+function Say($m) { Write-Host "[ga-deploy] $m" -ForegroundColor Cyan }
+
+function Ok($m) { Write-Host "[ok] $m" -ForegroundColor Green }
+
+function Die($m) { Write-Host "[error] $m" -ForegroundColor Red; exit 1 }
+
+function Invoke-Native([scriptblock]$Command) {
+
+ $prevEAP = $ErrorActionPreference
+
+ $ErrorActionPreference = 'Continue'
+
+ try { & $Command } finally { $ErrorActionPreference = $prevEAP }
+
+ return $LASTEXITCODE
+
+}
+
+
+
+function Download-File($Url, $OutFile) {
+
+ New-Item -ItemType Directory -Force -Path (Split-Path $OutFile) | Out-Null
+
+ Say "Downloading $Url"
+
+ $wc = New-Object System.Net.WebClient
+
+ $wc.Headers.Add("User-Agent", "Mozilla/5.0 ga-deploy")
+
+ try { $wc.DownloadFile($Url, $OutFile) } finally { $wc.Dispose() }
+
+ if (!(Test-Path $OutFile) -or ((Get-Item $OutFile).Length -lt 1024)) { Die "Download failed: $Url" }
+
+}
+
+
+
+function Expand-ZipClean($Zip, $Dest) {
+
+ if (Test-Path $Dest) { Remove-Item -Recurse -Force $Dest }
+
+ New-Item -ItemType Directory -Force -Path $Dest | Out-Null
+
+ Expand-Archive -Path $Zip -DestinationPath $Dest -Force
+
+}
+
+
+
+function Copy-DirectoryContents($Src, $Dst) {
+
+ New-Item -ItemType Directory -Force -Path $Dst | Out-Null
+
+ Get-ChildItem -LiteralPath $Src -Force | ForEach-Object {
+
+ Copy-Item -LiteralPath $_.FullName -Destination $Dst -Recurse -Force
+
+ }
+
+}
+
+
+
+Say "Install dir: $GaDir"
+
+Say "Mode: $(if ($GlobalMode) { 'GLOBAL=1 / GitHub clone' } else { 'Mainland / VPS zip' })"
+
+
+
+if ((Test-Path $GaDir) -and $Force) { Remove-Item -Recurse -Force $GaDir }
+
+New-Item -ItemType Directory -Force -Path $GaDir,$PortableRoot,$Bin,$Cache,$Tools | Out-Null
+
+
+
+# uv (GitHub release in GLOBAL mode, user's VPS otherwise)
+
+if (!(Test-Path $UvExe) -or $Force) {
+
+ Download-File $UvUrl $UvZip
+
+ Expand-ZipClean $UvZip $UvExtract
+
+ $foundUv = Get-ChildItem -Path $UvExtract -Recurse -Filter "uv.exe" | Select-Object -First 1
+
+ if (!$foundUv) { Die "uv.exe not found in archive" }
+
+ Copy-Item $foundUv.FullName $UvExe -Force
+
+}
+
+Ok "uv: $(& $UvExe --version)"
+
+
+
+# Configure portable Python location. Mirror only in mainland mode.
+
+$env:UV_PYTHON_INSTALL_DIR = Join-Path $PortableRoot "uv-python"
+
+$env:UV_CACHE_DIR = Join-Path $PortableRoot "uv-cache"
+
+if ($GlobalMode) {
+
+ Remove-Item Env:UV_DEFAULT_INDEX -ErrorAction SilentlyContinue
+
+ Remove-Item Env:PIP_INDEX_URL -ErrorAction SilentlyContinue
+
+} else {
+
+ $env:UV_DEFAULT_INDEX = $MainlandIndex
+
+ $env:PIP_INDEX_URL = $MainlandIndex
+
+}
+
+$env:PATH = "$Bin;$env:PATH"
+
+
+
+
+# Workaround: uv creates minor-version symlinks (junctions) in UV_PYTHON_INSTALL_DIR.
+# If a previous interrupted install left a plain directory, uv fails with os error 4390.
+# Fix: remove any non-junction subdirectory so uv can recreate them cleanly.
+$uvPyDir = $env:UV_PYTHON_INSTALL_DIR
+if (Test-Path $uvPyDir) {
+ Get-ChildItem -LiteralPath $uvPyDir -Directory | ForEach-Object {
+ $attr = $_.Attributes
+ if (($attr -band [IO.FileAttributes]::ReparsePoint) -eq 0) {
+ Say "Removing stale non-junction dir: $($_.Name)"
+ Remove-Item -LiteralPath $_.FullName -Recurse -Force
+ }
+ }
+}
+
+Say "Installing Python $PythonVersion via uv"
+
+$ec = Invoke-Native { & $UvExe python install $PythonVersion }
+
+if ($ec -ne 0) { Die "uv python install failed" }
+
+$PythonExe = (& $UvExe python find $PythonVersion).Trim()
+
+if (!(Test-Path $PythonExe)) { Die "uv installed Python but python.exe was not found" }
+
+Ok "Python: $(& $PythonExe --version)"
+
+
+
+# PortableGit (GitHub release in GLOBAL mode, user's VPS otherwise). Needed for GLOBAL=1 and useful for user shell.
+
+if (!(Test-Path $GitExe) -or $Force) {
+
+ Download-File $GitUrl $GitExeArchive
+
+ if (Test-Path $GitDir) { Remove-Item -Recurse -Force $GitDir }
+
+ New-Item -ItemType Directory -Force -Path $GitDir | Out-Null
+
+ Say "Extracting PortableGit"
+
+ $ec = Invoke-Native { & $GitExeArchive -y -o"$GitDir" | Out-Null }
+
+ if ($ec -ne 0) { Die "PortableGit extraction failed" }
+
+}
+
+if (!(Test-Path $GitExe)) { Die "git.exe missing: $GitExe" }
+
+Ok "Git: $(& $GitExe --version)"
+
+
+
+$PythonDir = Split-Path $PythonExe -Parent
+
+$GitBin = Split-Path $GitExe -Parent
+
+$GitUsrBin = Join-Path $GitDir "usr\bin"
+
+$env:PATH = "$Bin;$PythonDir;$PythonDir\Scripts;$GitBin;$GitUsrBin;$env:PATH"
+
+
+
+# Fetch/update GenericAgent source.
+
+if ($GlobalMode) {
+
+ Say "Cloning GenericAgent from GitHub"
+
+ $items = @(Get-ChildItem -LiteralPath $GaDir -Force -ErrorAction SilentlyContinue | Where-Object { $_.Name -ne ".portable" })
+
+ if ($items.Count -gt 0) {
+
+ if (!$Force) { Die "Install dir contains files. Re-run with -Force to replace source while preserving portable tools." }
+
+ $items | Remove-Item -Recurse -Force
+
+ }
+
+ $TmpClone = Join-Path $Cache "ga-clone"
+
+ if (Test-Path $TmpClone) { Remove-Item -Recurse -Force $TmpClone }
+
+ $ec = Invoke-Native { & $GitExe clone --depth 1 $RepoUrl $TmpClone }
+
+ if ($ec -ne 0) { Die "git clone failed" }
+
+ Copy-DirectoryContents $TmpClone $GaDir
+
+ Remove-Item -Recurse -Force $TmpClone
+
+} else {
+
+ Say "Downloading GenericAgent package from VPS"
+
+ Download-File $GaZipUrl $GaZip
+
+ Expand-ZipClean $GaZip $GaExtract
+
+ $SrcDir = Join-Path $GaExtract "GenericAgent"
+
+ if (!(Test-Path $SrcDir)) { $SrcDir = $GaExtract }
+
+ $items = @(Get-ChildItem -LiteralPath $GaDir -Force -ErrorAction SilentlyContinue | Where-Object { $_.Name -ne ".portable" })
+
+ if ($items.Count -gt 0) { $items | Remove-Item -Recurse -Force }
+
+ Copy-DirectoryContents $SrcDir $GaDir
+
+}
+
+Ok "GenericAgent source ready: $GaDir"
+
+
+
+# Install basic dependencies and project in editable mode into portable Python.
+
+Say "Installing GenericAgent dependencies via uv pip"
+
+$installArgs = @("pip", "install", "--break-system-packages", "--python", $PythonExe)
+
+if (!$GlobalMode) { $installArgs += @("--index-url", $MainlandIndex) }
+
+$installArgs += $Deps
+
+$ec = Invoke-Native { & $UvExe @installArgs }
+
+if ($ec -ne 0) { Die "dependency install failed" }
+
+
+
+if (Test-Path (Join-Path $GaDir "pyproject.toml")) {
+
+ $projectArgs = @("pip", "install", "--break-system-packages", "--python", $PythonExe)
+
+ if (!$GlobalMode) { $projectArgs += @("--index-url", $MainlandIndex) }
+
+ $projectArgs += @("-e", $GaDir)
+
+ $ec = Invoke-Native { & $UvExe @projectArgs }
+
+ if ($ec -ne 0) { Die "editable project install failed" }
+
+}
+
+
+
+# Try-install pywebview (optional UI). Failure is non-fatal.
+
+Say "Attempting to install pywebview (optional, failure is OK)"
+
+$webviewArgs = @("pip", "install", "--break-system-packages", "--python", $PythonExe)
+
+if (!$GlobalMode) { $webviewArgs += @("--index-url", $MainlandIndex) }
+
+$webviewArgs += @("pywebview>=4.0")
+
+$ec = Invoke-Native { & $UvExe @webviewArgs 2>&1 | Out-Null }
+
+if ($ec -ne 0) {
+
+ Write-Host "[warn] pywebview install failed. This is optional." -ForegroundColor Yellow
+
+ Write-Host " On Windows it usually works out of the box." -ForegroundColor Yellow
+
+ Write-Host " If needed later: uv pip install pywebview" -ForegroundColor Yellow
+
+} else {
+
+ Ok "pywebview installed successfully"
+
+}
+
+
+
+# Activation scripts: portable paths are intentionally before system PATH.
+
+if ($GlobalMode) {
+
+@"
+
+@echo off
+
+set "PORTABLE_DEV_ROOT=$PortableRoot"
+
+set "GENERICAGENT_HOME=$GaDir"
+
+set "UV_PYTHON_INSTALL_DIR=$PortableRoot\uv-python"
+
+set "UV_CACHE_DIR=$PortableRoot\uv-cache"
+
+set "PATH=$Bin;$PythonDir;$PythonDir\Scripts;$GitBin;$GitUsrBin;%PATH%"
+
+echo Activated GenericAgent portable env: %GENERICAGENT_HOME%
+
+"@ | Set-Content -Path $EnvCmd -Encoding ASCII
+
+
+
+@"
+
+`$env:PORTABLE_DEV_ROOT = "$PortableRoot"
+
+`$env:GENERICAGENT_HOME = "$GaDir"
+
+`$env:UV_PYTHON_INSTALL_DIR = "$PortableRoot\uv-python"
+
+`$env:UV_CACHE_DIR = "$PortableRoot\uv-cache"
+
+`$env:PATH = "$Bin;$PythonDir;$PythonDir\Scripts;$GitBin;$GitUsrBin;`$env:PATH"
+
+Write-Host "Activated GenericAgent portable env: `$env:GENERICAGENT_HOME" -ForegroundColor Green
+
+"@ | Set-Content -Path $EnvPs1 -Encoding UTF8
+
+} else {
+
+@"
+
+@echo off
+
+set "PORTABLE_DEV_ROOT=$PortableRoot"
+
+set "GENERICAGENT_HOME=$GaDir"
+
+set "UV_PYTHON_INSTALL_DIR=$PortableRoot\uv-python"
+
+set "UV_CACHE_DIR=$PortableRoot\uv-cache"
+
+set "UV_DEFAULT_INDEX=$MainlandIndex"
+
+set "PIP_INDEX_URL=$MainlandIndex"
+
+set "PATH=$Bin;$PythonDir;$PythonDir\Scripts;$GitBin;$GitUsrBin;%PATH%"
+
+echo Activated GenericAgent portable env: %GENERICAGENT_HOME%
+
+"@ | Set-Content -Path $EnvCmd -Encoding ASCII
+
+
+
+@"
+
+`$env:PORTABLE_DEV_ROOT = "$PortableRoot"
+
+`$env:GENERICAGENT_HOME = "$GaDir"
+
+`$env:UV_PYTHON_INSTALL_DIR = "$PortableRoot\uv-python"
+
+`$env:UV_CACHE_DIR = "$PortableRoot\uv-cache"
+
+`$env:UV_DEFAULT_INDEX = "$MainlandIndex"
+
+`$env:PIP_INDEX_URL = "$MainlandIndex"
+
+`$env:PATH = "$Bin;$PythonDir;$PythonDir\Scripts;$GitBin;$GitUsrBin;`$env:PATH"
+
+Write-Host "Activated GenericAgent portable env: `$env:GENERICAGENT_HOME" -ForegroundColor Green
+
+"@ | Set-Content -Path $EnvPs1 -Encoding UTF8
+
+}
+
+
+
+Ok "Verification:"
+
+& $UvExe --version
+
+& $PythonExe --version
+
+& $GitExe --version
+
+& $PythonExe -c "import requests, bs4, bottle; print('deps ok')"
+
+Write-Host ""
+
+
+
+# Copy mykey template if mykey.py does not exist (GLOBAL mode only)
+
+$MykeyDst = Join-Path $GaDir "mykey.py"
+
+if ($GlobalMode -and !(Test-Path $MykeyDst)) {
+
+ $MykeyTpl = Join-Path $GaDir "mykey_template_en.py"
+
+ if (Test-Path $MykeyTpl) {
+
+ Copy-Item $MykeyTpl $MykeyDst
+
+ Ok "Copied mykey_template_en.py -> mykey.py"
+
+ }
+
+}
+
+
+
+# Final banner
+
+Write-Host ""
+
+if ($GlobalMode) {
+
+ Write-Host @"
+
+╔═══════════════════════════════════════════════╗
+
+║ ✅ GenericAgent installed successfully! ║
+
+╠═══════════════════════════════════════════════╣
+
+║ 📁 Location: $GaDir
+
+║ 🔑 Config: edit mykey.py (copied from template)
+
+║ 🚀 Launch: ga tui / ga launch / ga hub
+
+╚═══════════════════════════════════════════════╝
+
+"@
+
+} else {
+
+ Write-Host @"
+
+╔═══════════════════════════════════════════════╗
+
+║ [OK] GenericAgent 安装完成! ║
+
+╠═══════════════════════════════════════════════╣
+
+║ 安装目录: $GaDir
+
+║ 配置密钥: ga configure
+
+║ 启动: ga tui / ga launch / ga hub
+
+╚═══════════════════════════════════════════════╝
+
+"@
+
+}
+
+Write-Host ""
+
+Write-Host " Activate env: cmd.exe → call `"$EnvCmd`" | PowerShell → . `"$EnvPs1`""
+
diff --git a/assets/ga_install.sh b/assets/ga_install.sh
new file mode 100644
index 00000000..df6ad322
--- /dev/null
+++ b/assets/ga_install.sh
@@ -0,0 +1,289 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+# GenericAgent one-click portable deployer for macOS/Linux.
+# Modes:
+# Default/Mainland: download GenericAgent.zip + uv from user's VPS, set China PyPI mirror.
+# GLOBAL=1: clone GenericAgent from GitHub; uv also from GitHub releases; no PyPI mirror.
+# Portable components are installed under /.portable:
+# uv, Python installed by uv. On macOS/Linux git is expected from system package manager.
+
+INSTALL_DIR="${INSTALL_DIR:-$HOME/GenericAgent}"
+PYTHON_VERSION="${PYTHON_VERSION:-3.12}"
+FORCE="${FORCE:-0}"
+GLOBAL="${GLOBAL:-0}"
+
+REPO_URL="https://github.com/lsdefine/GenericAgent.git"
+VPS_BASE="http://47.101.182.29:9000"
+GA_ZIP_URL="$VPS_BASE/files/GenericAgent.zip"
+MAINLAND_INDEX="https://pypi.tuna.tsinghua.edu.cn/simple"
+DEPS=("requests>=2.28" "beautifulsoup4>=4.12" "bottle>=0.12" "simple-websocket-server>=0.4" "streamlit>=1.28")
+
+say(){ printf '\033[36m[ga-deploy]\033[0m %s\n' "$*"; }
+ok(){ printf '\033[32m[ok]\033[0m %s\n' "$*"; }
+die(){ printf '\033[31m[error]\033[0m %s\n' "$*" >&2; exit 1; }
+
+usage(){ cat <<'EOF'
+Usage:
+ bash install_portable_env.sh
+ INSTALL_DIR="$HOME/GenericAgent" PYTHON_VERSION=3.12 FORCE=1 bash install_portable_env.sh
+ GLOBAL=1 bash install_portable_env.sh
+
+Environment variables:
+ INSTALL_DIR Install GenericAgent here. Default: ~/GenericAgent
+ PYTHON_VERSION Python version installed by uv. Default: 3.12
+ FORCE=1 Replace existing source files while preserving/reinstalling portable tools.
+ GLOBAL=1 Clone from GitHub directly and do not set China PyPI mirror.
+EOF
+}
+
+if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then usage; exit 0; fi
+
+OS="$(uname -s)"
+ARCH="$(uname -m)"
+case "$OS:$ARCH" in
+ Darwin:x86_64) uv_file="uv-x86_64-apple-darwin.tar.gz" ;;
+ Darwin:arm64) uv_file="uv-aarch64-apple-darwin.tar.gz" ;;
+ Linux:x86_64) uv_file="uv-x86_64-unknown-linux-gnu.tar.gz" ;;
+ Linux:aarch64|Linux:arm64) uv_file="uv-aarch64-unknown-linux-gnu.tar.gz" ;;
+ *) die "Unsupported platform: $OS $ARCH" ;;
+esac
+
+GA_DIR="${INSTALL_DIR/#\~/$HOME}"
+case "$GA_DIR" in
+ /*) ;;
+ *) GA_DIR="$PWD/$GA_DIR" ;;
+esac
+mkdir -p "$GA_DIR"
+GA_DIR="$(cd "$GA_DIR" && pwd -P)"
+PORTABLE_ROOT="$GA_DIR/.portable"
+BIN="$PORTABLE_ROOT/bin"
+CACHE="$PORTABLE_ROOT/cache"
+UV_TGZ="$CACHE/$uv_file"
+GA_ZIP="$CACHE/GenericAgent.zip"
+UV_EXTRACT="$CACHE/uv-extract"
+GA_EXTRACT="$CACHE/ga-extract"
+UV_EXE="$BIN/uv"
+ENV_SH="$GA_DIR/env.sh"
+
+mkdir -p "$GA_DIR" "$PORTABLE_ROOT" "$BIN" "$CACHE"
+
+say "Install dir: $GA_DIR"
+if [[ "$GLOBAL" == "1" ]]; then say "Mode: GLOBAL=1 / GitHub clone"; else say "Mode: Mainland / VPS zip"; fi
+
+if [[ "$FORCE" == "1" ]]; then
+ # Preserve .portable if present; remove source files later before deploying.
+ :
+fi
+
+download(){
+ local url="$1" out="$2"
+ mkdir -p "$(dirname "$out")"
+ say "Downloading $url"
+ if command -v curl >/dev/null 2>&1; then
+ curl -fL --retry 3 -A "ga-deploy" -o "$out" "$url"
+ elif command -v wget >/dev/null 2>&1; then
+ wget -O "$out" "$url"
+ else
+ die "curl or wget is required"
+ fi
+ [[ -s "$out" ]] || die "Download failed: $url"
+}
+
+extract_tgz_clean(){
+ local tgz="$1" dest="$2"
+ rm -rf "$dest"; mkdir -p "$dest"
+ tar -xzf "$tgz" -C "$dest"
+}
+
+extract_zip_clean(){
+ local zip="$1" dest="$2"
+ rm -rf "$dest"; mkdir -p "$dest"
+ if command -v unzip >/dev/null 2>&1; then
+ unzip -q "$zip" -d "$dest"
+ else
+ python3 - "$zip" "$dest" <<'PY'
+import sys, zipfile
+with zipfile.ZipFile(sys.argv[1]) as z:
+ z.extractall(sys.argv[2])
+PY
+ fi
+}
+
+copy_contents(){
+ local src="$1" dst="$2"
+ mkdir -p "$dst"
+ (cd "$src" && tar -cf - .) | (cd "$dst" && tar -xf -)
+}
+
+remove_source_files(){
+ shopt -s dotglob nullglob
+ for p in "$GA_DIR"/*; do
+ [[ "$(basename "$p")" == ".portable" ]] && continue
+ rm -rf "$p"
+ done
+ shopt -u dotglob nullglob
+}
+
+# uv: GitHub release in GLOBAL mode, user's VPS otherwise
+if [[ ! -x "$UV_EXE" || "$FORCE" == "1" ]]; then
+ if [[ "$GLOBAL" == "1" ]]; then
+ download "https://github.com/astral-sh/uv/releases/latest/download/$uv_file" "$UV_TGZ"
+ else
+ download "$VPS_BASE/uv/$uv_file" "$UV_TGZ"
+ fi
+ extract_tgz_clean "$UV_TGZ" "$UV_EXTRACT"
+ found_uv="$(find "$UV_EXTRACT" -type f -name uv | head -n 1 || true)"
+ [[ -n "$found_uv" ]] || die "uv not found in archive"
+ cp "$found_uv" "$UV_EXE"
+ chmod +x "$UV_EXE"
+fi
+ok "uv: $($UV_EXE --version)"
+
+export UV_PYTHON_INSTALL_DIR="$PORTABLE_ROOT/uv-python"
+export UV_CACHE_DIR="$PORTABLE_ROOT/uv-cache"
+if [[ "$GLOBAL" == "1" ]]; then
+ unset UV_DEFAULT_INDEX PIP_INDEX_URL
+else
+ export UV_DEFAULT_INDEX="$MAINLAND_INDEX"
+ export PIP_INDEX_URL="$MAINLAND_INDEX"
+fi
+export PATH="$BIN:$PATH"
+
+say "Installing Python $PYTHON_VERSION via uv"
+"$UV_EXE" python install "$PYTHON_VERSION"
+PYTHON_EXE="$($UV_EXE python find "$PYTHON_VERSION")"
+[[ -x "$PYTHON_EXE" ]] || die "uv installed Python but executable was not found"
+ok "Python: $($PYTHON_EXE --version)"
+PYTHON_DIR="$(dirname "$PYTHON_EXE")"
+export PATH="$BIN:$PYTHON_DIR:$PATH"
+
+# git: macOS/Linux use system git. In mainland mode it is not required for source fetch.
+GIT_EXE=""
+if command -v git >/dev/null 2>&1; then
+ GIT_EXE="$(command -v git)"
+ ok "git: $($GIT_EXE --version)"
+elif [[ "$GLOBAL" == "1" ]]; then
+ die "GLOBAL=1 requires git. Install git with your system package manager, then rerun."
+else
+ say "git not found; continuing because mainland mode uses VPS zip. Install git later if needed."
+fi
+
+# Fetch/update GenericAgent source.
+if [[ "$GLOBAL" == "1" ]]; then
+ say "Cloning GenericAgent from GitHub"
+ if [[ -n "$(find "$GA_DIR" -mindepth 1 -maxdepth 1 ! -name .portable -print -quit)" ]]; then
+ [[ "$FORCE" == "1" ]] || die "Install dir contains files. Re-run with FORCE=1 to replace source while preserving portable tools."
+ remove_source_files
+ fi
+ TMP_CLONE="$CACHE/ga-clone"
+ rm -rf "$TMP_CLONE"
+ "$GIT_EXE" clone --depth 1 "$REPO_URL" "$TMP_CLONE"
+ copy_contents "$TMP_CLONE" "$GA_DIR"
+ rm -rf "$TMP_CLONE"
+else
+ say "Downloading GenericAgent package from VPS"
+ download "$GA_ZIP_URL" "$GA_ZIP"
+ extract_zip_clean "$GA_ZIP" "$GA_EXTRACT"
+ SRC_DIR="$GA_EXTRACT/GenericAgent"
+ [[ -d "$SRC_DIR" ]] || SRC_DIR="$GA_EXTRACT"
+ remove_source_files
+ copy_contents "$SRC_DIR" "$GA_DIR"
+fi
+ok "GenericAgent source ready: $GA_DIR"
+
+# Install basic dependencies and project in editable mode into portable Python.
+say "Installing GenericAgent dependencies via uv pip"
+install_args=(pip install --python "$PYTHON_EXE" --break-system-packages)
+if [[ "$GLOBAL" != "1" ]]; then install_args+=(--index-url "$MAINLAND_INDEX"); fi
+install_args+=("${DEPS[@]}")
+"$UV_EXE" "${install_args[@]}"
+
+if [[ -f "$GA_DIR/pyproject.toml" ]]; then
+ project_args=(pip install --python "$PYTHON_EXE" --break-system-packages)
+ if [[ "$GLOBAL" != "1" ]]; then project_args+=(--index-url "$MAINLAND_INDEX"); fi
+ project_args+=(-e "$GA_DIR")
+ "$UV_EXE" "${project_args[@]}"
+fi
+
+# Try-install pywebview (optional UI). Failure is non-fatal.
+say "Attempting to install pywebview (optional, failure is OK)"
+webview_args=(pip install --python "$PYTHON_EXE" --break-system-packages)
+if [[ "$GLOBAL" != "1" ]]; then webview_args+=(--index-url "$MAINLAND_INDEX"); fi
+webview_args+=("pywebview>=4.0")
+if "$UV_EXE" "${webview_args[@]}" 2>/dev/null; then
+ ok "pywebview installed successfully"
+else
+ printf '\033[33m[warn]\033[0m pywebview install failed. This is optional.\n'
+ printf ' On Linux, pywebview requires system GTK/WebKit libraries.\n'
+ printf ' Install them first, e.g.:\n'
+ printf ' Debian/Ubuntu: sudo apt install python3-gi gir1.2-webkit2-4.1 libgirepository1.0-dev\n'
+ printf ' Fedora: sudo dnf install python3-gobject webkit2gtk4.1\n'
+ printf ' macOS: usually works out of the box (uses PyObjC)\n'
+ printf ' Then retry: uv pip install pywebview\n'
+fi
+
+# Activation script: portable paths are intentionally before system PATH.
+if [[ "$GLOBAL" == "1" ]]; then
+ cat > "$ENV_SH" < "$ENV_SH" < mykey.py"
+ fi
+fi
+
+# Final banner
+echo ""
+if [[ "$GLOBAL" == "1" ]]; then
+ cat <