docs+fix: README install overhaul, self-hosted install scripts, TMWebDriver logging fix
- README (EN): clone+uv as recommended install; one-line installer demoted to convenience with script source link; drop unpolished exe section; recommend frontends/tui_v3.py (prompt_toolkit+rich, not Textual); note agent core's single dependency (requests); TMWebdriver naming - assets: add ga_install.ps1/.sh (GLOBAL mode uses GitHub/astral-sh sources) - TMWebDriver.py: don't let logging failure mask successful remote JS result
This commit is contained in:
1 parent
efda2c3202
commit
8a823c940e
4 files changed
+905
-45
No files matched your search
@@ -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
|
||||
```
|
||||
|
||||
<details>
|
||||
@@ -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."*
|
||||
|
||||
</details>
|
||||
|
||||
@@ -362,9 +357,9 @@ Baselines across these dimensions include **Claude Code**, **OpenAI CodeX**, and
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### 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 |
|
||||
| :--- | :---: | :---: | :--- |
|
||||
|
||||
+5
-6
@@ -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
|
||||
|
||||
@@ -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 <InstallDir>\.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`""
|
||||
|
||||
@@ -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 <INSTALL_DIR>/.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" <<EOF
|
||||
export PORTABLE_DEV_ROOT="$PORTABLE_ROOT"
|
||||
export GENERICAGENT_HOME="$GA_DIR"
|
||||
export UV_PYTHON_INSTALL_DIR="$PORTABLE_ROOT/uv-python"
|
||||
export UV_CACHE_DIR="$PORTABLE_ROOT/uv-cache"
|
||||
export PATH="$BIN:$PYTHON_DIR:\$PATH"
|
||||
echo "Activated GenericAgent portable env: \$GENERICAGENT_HOME"
|
||||
EOF
|
||||
else
|
||||
cat > "$ENV_SH" <<EOF
|
||||
export PORTABLE_DEV_ROOT="$PORTABLE_ROOT"
|
||||
export GENERICAGENT_HOME="$GA_DIR"
|
||||
export UV_PYTHON_INSTALL_DIR="$PORTABLE_ROOT/uv-python"
|
||||
export UV_CACHE_DIR="$PORTABLE_ROOT/uv-cache"
|
||||
export UV_DEFAULT_INDEX="$MAINLAND_INDEX"
|
||||
export PIP_INDEX_URL="$MAINLAND_INDEX"
|
||||
export PATH="$BIN:$PYTHON_DIR:\$PATH"
|
||||
echo "Activated GenericAgent portable env: \$GENERICAGENT_HOME"
|
||||
EOF
|
||||
fi
|
||||
|
||||
ok "Verification:"
|
||||
"$UV_EXE" --version
|
||||
"$PYTHON_EXE" --version
|
||||
if [[ -n "$GIT_EXE" ]]; then "$GIT_EXE" --version; fi
|
||||
"$PYTHON_EXE" -c "import requests, bs4, bottle; print('deps ok')"
|
||||
|
||||
# Copy mykey template if mykey.py does not exist (GLOBAL mode only)
|
||||
MYKEY_DST="$GA_DIR/mykey.py"
|
||||
if [[ "$GLOBAL" == "1" && ! -f "$MYKEY_DST" ]]; then
|
||||
MYKEY_TPL="$GA_DIR/mykey_template_en.py"
|
||||
if [[ -f "$MYKEY_TPL" ]]; then
|
||||
cp "$MYKEY_TPL" "$MYKEY_DST"
|
||||
ok "Copied mykey_template_en.py -> mykey.py"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Final banner
|
||||
echo ""
|
||||
if [[ "$GLOBAL" == "1" ]]; then
|
||||
cat <<EOF
|
||||
╔═══════════════════════════════════════════════╗
|
||||
║ ✅ GenericAgent installed successfully! ║
|
||||
╠═══════════════════════════════════════════════╣
|
||||
║ 📁 Location: $GA_DIR
|
||||
║ 🔑 Config: edit mykey.py (copied from template)
|
||||
║ 🚀 Launch: ga tui / ga launch / ga hub
|
||||
╚═══════════════════════════════════════════════╝
|
||||
EOF
|
||||
else
|
||||
cat <<EOF
|
||||
╔═══════════════════════════════════════════════╗
|
||||
║ ✅ GenericAgent 安装完成! ║
|
||||
╠═══════════════════════════════════════════════╣
|
||||
║ 📁 安装目录: $GA_DIR
|
||||
║ 🔑 配置密钥: ga configure
|
||||
║ 🚀 启动: ga tui / ga launch / ga hub
|
||||
╚═══════════════════════════════════════════════╝
|
||||
EOF
|
||||
fi
|
||||
echo ""
|
||||
ok "Activate env: source \"$ENV_SH\""
|
||||
Reference in new issue
Block a user