From 9dec38db604e8e089d83de8a8b27c6e3c95dde1d Mon Sep 17 00:00:00 2001 From: chakcy <947105045@qq.com> Date: Sun, 10 May 2026 10:56:52 +0800 Subject: [PATCH] first commit --- .env.example | 11 + .gitignore | 14 + .python-version | 1 + README.md | 314 ++ chongming.ico | Bin 0 -> 237133 bytes docs/docker使用手册.md | 754 +++++ docs/git使用手册.md | 431 +++ docs/images/gui.png | Bin 0 -> 67161 bytes docs/nvm使用文档.md | 189 ++ docs/uv使用文档.md | 424 +++ gui.spec | 55 + pyproject.toml | 32 + resources/chongming_dev.sh | 116 + resources/deploy_docker.sh | 189 ++ resources/deploy_git.sh | 42 + resources/deploy_nvm.sh | 133 + resources/deploy_uv.sh | 151 + scripts/add_script_runner.py | 281 ++ scripts/add_script_tab.py | 275 ++ scripts/check_syntax.py | 18 + scripts/clean_pycache.py | 13 + scripts/code_content.py | 45 + scripts/fix_paths.py | 30 + scripts/fix_upload_folder.py | 226 ++ src/virtualbox_client/__init__.py | 0 src/virtualbox_client/gui.py | 2568 +++++++++++++++++ src/virtualbox_client/scripts/__init__.py | 0 src/virtualbox_client/scripts/agent_task.py | 59 + src/virtualbox_client/scripts/example.py | 49 + src/virtualbox_client/scripts/run_script.py | 137 + src/virtualbox_client/utils/__init__.py | 0 src/virtualbox_client/utils/ssh_manager.py | 264 ++ .../utils/vbox_ssh_manager.py | 385 +++ src/virtualbox_client/utils/vm_agent.py | 336 +++ uv.lock | 664 +++++ 35 files changed, 8206 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 .python-version create mode 100644 README.md create mode 100644 chongming.ico create mode 100644 docs/docker使用手册.md create mode 100644 docs/git使用手册.md create mode 100644 docs/images/gui.png create mode 100644 docs/nvm使用文档.md create mode 100644 docs/uv使用文档.md create mode 100644 gui.spec create mode 100644 pyproject.toml create mode 100644 resources/chongming_dev.sh create mode 100644 resources/deploy_docker.sh create mode 100644 resources/deploy_git.sh create mode 100644 resources/deploy_nvm.sh create mode 100644 resources/deploy_uv.sh create mode 100644 scripts/add_script_runner.py create mode 100644 scripts/add_script_tab.py create mode 100644 scripts/check_syntax.py create mode 100644 scripts/clean_pycache.py create mode 100644 scripts/code_content.py create mode 100644 scripts/fix_paths.py create mode 100644 scripts/fix_upload_folder.py create mode 100644 src/virtualbox_client/__init__.py create mode 100644 src/virtualbox_client/gui.py create mode 100644 src/virtualbox_client/scripts/__init__.py create mode 100644 src/virtualbox_client/scripts/agent_task.py create mode 100644 src/virtualbox_client/scripts/example.py create mode 100644 src/virtualbox_client/scripts/run_script.py create mode 100644 src/virtualbox_client/utils/__init__.py create mode 100644 src/virtualbox_client/utils/ssh_manager.py create mode 100644 src/virtualbox_client/utils/vbox_ssh_manager.py create mode 100644 src/virtualbox_client/utils/vm_agent.py create mode 100644 uv.lock diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..58b16ac --- /dev/null +++ b/.env.example @@ -0,0 +1,11 @@ +# 虚拟机连接信息 +VM_NAME=ubuntu20.04 +VM_SSH_HOST=127.0.0.1 +VM_SSH_PORT=2222 +VM_SSH_USER=chakcy +VM_SSH_PASS=020709djh + +# 大模型 API(OpenAI 或兼容接口) +LLM_API_KEY=sk-7b300b4948d04f208d1781c830f773ca +LLM_BASE_URL=https://api.deepseek.com # 若用本地模型可改为 http://localhost:11434/v1 +LLM_MODEL=deepseek-v4-flash \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..462918f --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# Python-generated files +__pycache__/ +*.py[oc] +build/ +dist/ +wheels/ +*.egg-info +*.log +output.md + +# Virtual environments +.venv +.sdk + diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..6324d40 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.14 diff --git a/README.md b/README.md new file mode 100644 index 0000000..549c28c --- /dev/null +++ b/README.md @@ -0,0 +1,314 @@ +# VirtualBox Client + +**VirtualBox 虚拟机自动化管理工具** — 通过 SSH 和 VirtualBox SDK 实现对 Ubuntu 20.04 虚拟机的全生命周期管理,支持 AI 智能任务执行、文件传输、脚本部署和图形化管理。 + +--- + +## ✨ 功能特性 + +- 🖥️ **虚拟机管理** — 启动、关闭、状态检测(VirtualBox SDK) +- 🔌 **纯 SSH 模式** — 不依赖 VirtualBox,直接通过 SSH 管理远程服务器 +- 🤖 **AI 智能代理** — 接入大语言模型(OpenAI API),通过自然语言描述自动执行运维任务 +- 📁 **文件传输** — 支持上传/下载文件、上传整个文件夹 +- 📂 **工作区浏览器** — 树形文件管理器,支持浏览、新建、重命名、复制、删除、下载文件夹(zip 打包) +- ⚡ **脚本执行** — 上传并执行 Shell 脚本,自动区分部署脚本(sudo)和普通脚本 +- 🎨 **图形界面** — 基于 PySide6 构建的现代化暗色主题 GUI +- 📋 **日志面板** — 带等级标识的彩色日志输出,支持自动滚动 + +--- + +## 📦 系统要求 + +| 依赖 | 说明 | +| -------------- | ------------------------ | +| Python | >= 3.14 | +| VirtualBox | 含 VirtualBox SDK(可选) | +| 操作系统 | Windows(主控端) | +| 目标虚拟机 | Ubuntu 20.04(受控端) | + +--- + +## 🚀 快速开始 + +### 1. 安装依赖 + +```powershell +# 同步 Python 依赖 +uv sync + +# 安装 VirtualBox SDK(仅 VirtualBox 模式需要) +# 确保已安装 VirtualBox,找到 sdk 路径后执行: +cd ./sdk/installer/python/ +uv run ./vboxapisetup.py install +``` + +> **📥 VirtualBox SDK 下载** +> +> 如果项目中尚未包含 SDK,请先下载 VirtualBox SDK 并解压到项目目录: +> +> ```powershell +> # 下载 VirtualBox SDK 7.2.8 +> curl -L https://download.virtualbox.org/virtualbox/7.2.8/VirtualBoxSDK-7.2.8-173730.zip -o VirtualBoxSDK.zip +> # 解压到项目根目录 +> tar -xf VirtualBoxSDK.zip +> ``` +> +> SDK 目录结构应包含 `sdk/installer/python/vboxapisetup.py`。 +> +> *注意:SDK 版本需与本地安装的 VirtualBox 版本匹配。* + +### 2. 配置环境变量 + +复制 `.env.example` 为 `.env`(或直接在 GUI 中填写): + +```env +# 虚拟机配置 +VM_NAME=ubuntu20.04 +VM_SSH_HOST=127.0.0.1 +VM_SSH_PORT=2222 +VM_SSH_USER=your_username +VM_SSH_PASS=your_password + +# 大模型配置(AI 代理模式需要) +LLM_API_KEY=sk-your-api-key +LLM_BASE_URL=https://api.openai.com +LLM_MODEL=gpt-4o +``` + +### 3. 启动方式 + +#### 🖥️ 图形界面 + +```powershell +uv run gui +``` + +![GUI Screenshot](docs/images/gui.png) *(screenshot placeholder)* + +#### ⌨️ 命令行示例 + +```powershell +uv run example +``` + +--- + +## 📖 使用指南 + +### 配置面板 + +左侧配置面板包含三个区域: + +1. **运行模式** — 切换「纯 SSH 模式」或「VirtualBox 模式」 +2. **虚拟机配置** — SSH 连接信息(主机、端口、用户名、密码) +3. **大模型配置** — AI 代理的 API Key、Base URL 和模型名称 + +### 任务执行面板 + +在「任务执行」Tab 中,输入自然语言描述,AI 代理将自动解析并执行: + +``` +在 /home/user/myproject 下有一个 "myproject" 项目, +请安装 nginx 并确认服务状态 +``` + +系统会自动: +1. 启动虚拟机(VirtualBox 模式) +2. 连接 SSH +3. 调用 LLM 生成执行计划 +4. 逐步执行命令并反馈日志 +5. 任务完成后询问是否关闭虚拟机 + +### 文件传输 + +在「文件传输」Tab 中: +- **上传** — 选择本地文件,指定远程路径,点击上传 +- **下载** — 输入远程路径,选择本地保存位置,点击下载 + +### 脚本执行 + +在「Script」Tab 中: +1. 点击「Browse」选择本地的 `.sh` 脚本文件 +2. 点击「Upload and Execute Script」 +3. 脚本会自动上传到 `/tmp/`,执行完成后下载日志并清理临时文件 +4. 以 `deploy_` 开头的脚本自动使用 `sudo` 权限执行 + +### 工作区浏览器 + +在「工作区」Tab 中,可以像本地文件管理器一样操作远程文件: +- **双击文件夹** — 懒加载展开子目录 +- **单击文件** — 显示文件详情 +- **右键菜单** — 重命名、复制、删除、下载 +- **工具栏** — 上传文件、上传文件夹、新建文件夹、回到根目录 + +--- + +## 🛠️ 脚本开发 + +### 添加新脚本 + +在 `src/virtualbox_client/scripts/` 下创建 Python 文件: + +```python +# src/virtualbox_client/scripts/my_script.py + +def run(): + print("Hello from my script!") +``` + +在 `pyproject.toml` 中注册: + +```toml +[project.scripts] +my-script = "virtualbox_client.scripts.my_script:run" +``` + +重新构建并执行: + +```powershell +uv sync +uv run my-script +``` + +### 命令式脚本执行 + +```powershell +# 直接通过命令行运行脚本模块 +uv run -m virtualbox_client.scripts.my_script +``` + +--- + +## 📁 项目结构 + +``` +virtualbox_client/ +├── src/virtualbox_client/ +│ ├── __init__.py +│ ├── gui.py # PySide6 图形界面 +│ ├── scripts/ +│ │ ├── __init__.py +│ │ ├── agent_task.py # AI 代理任务执行 +│ │ ├── example.py # 使用示例 +│ │ └── run_script.py # 脚本运行器 +│ └── utils/ +│ ├── __init__.py +│ ├── ssh_manager.py # 纯 SSH 管理器 +│ ├── vbox_ssh_manager.py # VirtualBox + SSH 管理器 +│ └── vm_agent.py # AI 代理核心 +├── resources/ +│ ├── chongming_dev.sh +│ ├── deploy_docker.sh +│ ├── deploy_git.sh +│ ├── deploy_nvm.sh +│ └── deploy_uv.sh +├── scripts/ +│ ├── add_script_runner.py +│ ├── add_script_tab.py +│ ├── check_syntax.py +│ ├── clean_pycache.py +│ ├── code_content.py +│ ├── fix_paths.py +│ └── fix_upload_folder.py +├── docs/ +│ ├── docker使用手册.md +│ ├── git使用手册.md +│ ├── nvm使用文档.md +│ └── uv使用文档.md +├── .env.example # 环境变量模板 +├── .gitignore +├── .python-version +├── gui.spec # PyInstaller 打包配置 +├── pyproject.toml # 项目配置与依赖 +└── README.md +``` + +--- + +## 🧩 技术架构 + +### 后端 + +- **VirtualBox SDK** (`vboxapi`) — 通过 Windows COM 接口管理虚拟机生命周期 +- **paramiko** — SSH 连接与远程命令执行 +- **openai** — 大语言模型 API 调用(AI 代理模式) +- **pywin32** — Windows COM 互操作 + +### 前端 + +- **PySide6** — Qt6 的 Python 绑定,提供现代化 GUI +- **QThread** — 后台线程管理,避免 UI 阻塞 +- **QTreeWidget** — 带懒加载的远程文件浏览器 + +### 工作模式 + +``` +┌─────────────────────────────────────────┐ +│ VirtualBox Client │ +├─────────────────┬───────────────────────┤ +│ VirtualBox 模式 │ 纯 SSH 模式 │ +│ │ │ +│ VirtualBox │ SSH │ +│ SDK (COM) │ Manager │ +│ ↓ │ ↓ │ +│ VBoxSSHManager │ SSHManager │ +│ ↓ │ ↓ │ +│ Ubuntu VM │ 远程服务器 │ +└─────────────────┴───────────────────────┘ +``` + +--- + +## 🐳 部署与打包 + +### 使用 PyInstaller 打包为可执行文件 + +```powershell +uv run pyinstaller gui.spec +``` + +打包后的可执行文件位于 `dist/` 目录。 + +--- + +## ⚙️ 环境搭建详情 + +| 命令 | 说明 | +| ----------------------------------------- | ----------------------------- | +| `uv sync` | 同步所有 Python 依赖 | +| `uv run ./sdk/installer/python/vboxapisetup.py install` | 安装 VirtualBox SDK | +| `uv run gui` | 启动图形界面 | +| `uv run example` | 运行命令行示例 | +| `uv run agent-task` | 运行 AI 代理任务 | +| `uv run run-script` | 运行脚本 | +| `uv run pyinstaller gui.spec` | 打包为 EXE | + +--- + +## 📝 文档 + +详细使用文档请参考 `docs/` 目录: + +- [Docker 使用手册](docs/docker使用手册.md) +- [Git 使用手册](docs/git使用手册.md) +- [NVM 使用文档](docs/nvm使用文档.md) +- [UV 使用文档](docs/uv使用文档.md) + +--- + +## 🔧 常见问题 + +**Q: 启动报错 `CoInitialize` 相关错误?** +A: 这是 Windows COM 初始化问题,通常出现在 VirtualBox 模式下。确保已正确安装 VirtualBox SDK。 + +**Q: SSH 连接失败?** +A: 检查目标虚拟机是否已启动、SSH 服务是否运行、端口转发是否配置正确(默认 2222)。 + +**Q: AI 代理无响应?** +A: 确保 API Key、Base URL 和模型名称配置正确,且网络可以访问 LLM 服务。 + +--- + +## 📄 许可证 + +本项目仅供学习和参考使用。 diff --git a/chongming.ico b/chongming.ico new file mode 100644 index 0000000000000000000000000000000000000000..8e36272f41d38f6cb58f40f330b7005740dfa9fd GIT binary patch literal 237133 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%0ACnuR|)U34Ly+ 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

BKKewZ89+?#ur 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

-ExE;Q)ZrenXZ? zOf`Q^YF~Fg&zBA3DU)Y<&8Yx-)x`EBS3@6ZkT8AgjnkHcDg=dNQXfzQe1tIXv(&p` z<|B2p)4)WfN^4WGK2TwHLqMDKQ%{o-EWC1>_FI{Wkrw}gbTlG0dDHi}-Tw%fflJ#z zX4~f|m43)`G=mA5c5*kSLD92j^RNc>F_61~ZbV6l4q^gz_&}L_@?7SixZFG;%L00y zPt6eIHoJiCyS>VEh8hc{V)j=Z+j%eLGJqw;1vxsZ26y(>^bUSIGuir9$k65G#tehx zrze0T7bDpeWJlHoCv6KJ1Dxo)uPLwcK7e9Of@zF6tVg`+h42y}YG#>4n{}_+>`ggD6<-hkK zeE07Auu2rXnBMK3z8z!Gjp+Gk%e*4|$)2^SV#+23C{XWE+BPx*bj9>s|IWyqC1`2+tzZ<|Q9TV4K*2FmU{%thi&@D_CEHW(vjk9}?GeZUXG4eoZ z51@mRMS^7x#|A9(k#Xi7KebxEcZRv2HqR<>tqgSwoOdEwBe=&zPx%gv&pxo<*Ziyl zbS>DxN1#wpyx*x(8X29F)2KW6)^~lQP%u9IO6RWX+tKk_oH`ta%B230vr)m;K!~U z@rn1m4R;<^oE>v8Vjib3ENGigR-jHLrthuhu0QVubWgbfu+QXfE8og{(WD!~%m^X4 zU{)qd`T~IQ?@xUnuYCWXZ(#i^P%{LZ_{}#AiRcsY?c}+qNcUIa|Z5hR~RK z@`*;&GL32fW*=$db=vP0{Gcd_0P-}OU1?v?&bEZ=)Te>3fXUiT&;sZ}Gi63MA&szr z1sv?o0u3z4m~t87Wk42m(eerCJh<2SOEZ8`M!n&n3gXtV_Jnlt0c8!LYH1CQb> z-@60H+cVuED2dHzXG%n;%tX@n!D0M+PLN{5fb{2>f=Q}BCJQiH)2%O=K4{C-%sMd_ zMWdxb_n*gfn|i8HB{Xj*${ic%+2C%^%g=gT~3wKLM-g*L(X$A}r{?Bidw4+cO8s_EvK# zw&zRVN}LbjvhD-BCia-M%T~k*4)phYjRAd6EJ7`}mNtq5Xx6t3Z*bbe1n?2J-(bA< z;pgKo-~2`_hjZMvG9sOA3O`&F4`nTx2pQI#Za>>+)`vbzqkOmY=+`Y`wDx_fv)`M5 zv(9{m8Snl0XYrwb{#V=}WXX|1ybRV_Wu&t%(F$?WmivfB=%W}pMMOpe#+_hM3w20~ ztui<~3`CVz>bDJO-}4xC2<^B6PzOScQj*lijNfRp#XOpTx?w!!q&NqZv}UKZI?>ch z9oc*-T3qq3)~bs}*^xRDhIG3;u+MbFvh``z6DswyBwK(iqd=02V@(BC#kc-cxy}q zY%elgy9Aa3Yz>(2S;u{)#r4$8rcO8$+WNKI)4EaP7AWUkDD65H!8!?EBhKBOAu&nA z%@lTAWg6MKG_NZ+-zgUsqw)kIAQG;e{~tc~j$gtfKXiYdwQ(>NTERhoZ{kySLgx(7 zJun~fQ2l&_xx^{$mJIf$8PH#(BlgtSz7ugHkMqBL`)R!Dk3Nj=-+l#$034&IMQ{s< zJLuvp5t0Q4b0~t(z;^UO>8EUh6Z2NbJdjY>G4tt2P5{F_>FuOz%VFjo&>1M&2+8MJ z-whZn!vH!dARjSG9f006FOkJj(IRw@$hLLsC~mP7bPR}_>=y&{ykK)fx*4Oi z^M=7_L2v8kJOINBc?`JqnhsEaJZSTw+mNW@3K1bWTgv&aw>--!5`b}U9 z8S7wo`l9gHH3PX?W5f~53cTirui|fhPCx1hf_ zuAnRX*#dN+1=*3rVe^f?@0r}TUR`%&#yNn${=!%A&cAyM*Xb_MA;skc1Te{Fx2k2d zW^9=-W^bVe?-5z{?&Oqv+6L30LQ^gRU6tXsn_LIwz|s#T0K5rj?{vA8eb`6LAhXK&n}75H=eMSnRd*ykZ3a!5Xoq?^3v!N_{b^Xt~Kk?dzzMx z)~@miOxXjn^+4uh@9=d#n1$hc&p41jOvIdPv=r!aSxDPdH1?Kjd zt#6M)T*`|v@~r^#EmOeEhki;>#3{c0CLd7t>=LcG5d%K@#mDi^kA4zQt#<$mz(Hv~ z`PQ65HBn*m6l$QIJ}GSq=>5Y~-Y);N(11C^909t}-~hT8?*Q2GieoxOQ!mU-X`K2Z zhKp!sd*;QSJQ=uBgz+HR9TBINF9LuA>TO>trlfhe`Z;yyj(KgjE1LN+G_7_T_u4XB z4f5qEV--8n@M|;n<^3ILZ$52-(QUGGQk&d20lg5BtI%$;wF0_T4!kX*dn^MoH)euo z;V~&FaCY~B2Overuye3jS|qwUns(T+cPkt<%zU?d*tM{D0YLYxMYeB(w!~=K<=}KjYagnlfIh&zm@rmCUaD`Gy#Hc|p(+F7aWo!cr14IGr zF@YH)u~iv?DWzy=ll|tJ*v#cEtwkB-Y5y1^>PMakN-FQrucblO>EE(|Jq_J{?<^%I zX=XDty>BHIFX1UUqhPn(@7-D$7gcF0pqQw=G&Rq;o;X&=qmuK`Ab%LvgX^y8w@M}J3&3|1`?k*0A6swiX$;7CZXr&71Kg9dbMcI=)#qw5_eY0s3&@W<+-+Jd%6Z)RIPANy{jfL6gKXi_3 ze89gw`859cV~^oekADN-i3{SgAT9w!vNdV}miB;vDX0tI0x*uCSAes_2ijOUGN8JE zK6!Uf-&y}&+`Fp{XnonX%_ET10*Iq=xOp&GxEBF*U!31&3^Pezf1X)oiiKl?NrVho zMk8-5N?!3A07ifg7{=|EWN4;#Of*g2m~5rSN;2#7Rpk$L9gOPG&$Z?+X>X4AG*dpk=NC-{&`$x` zThR%l4!%|CBEEJ%PsBMBuJMX5eEnPatAF|&KKCC_;E8KTT%+3%DPYZK$_EaJAxZlL z$`Uf8l~B`vb=`Vk1r+JG5NLl_4{2lUo!|EW{9gs)^O6Ncrsk z{&%NKs(Efyw-y$d)g>5)>4ekfXBRs%prKqGqM3cxw4GA(UvrR=uEz*Cqa@oi(>o5> zk^-p?*lDwTO_!VE2yk2aw`L*wsTKIr*Zv2OKlvSe@xQ-~FMsO^eB=6cJaL`DD{$jHR<)KBz!zQ0 zW2{qE-$~T=Q{T`Q&{ABWN^Do#MDpe92e!>nXy71@v-Q z%?6lFE_JE>pvKDbWyv_QTqGh=rcnE;d4EuULyH1*p{o%p8lvQh(Bqs+X!FnN-=U@1 zeb|L=8VDf}Fi$|<{A#jpfm#Lok%eL?Z4ApC*5&eBwNO04+YPXl`UimS2>`S4^U9b# zYu+97Z6I8_dA9%Ww$-ci{{hSZxmrVQBy0cx002ovPDHLkV1l(iwBP^$ literal 0 HcmV?d00001 diff --git a/docs/docker使用手册.md b/docs/docker使用手册.md new file mode 100644 index 0000000..9ed3cd7 --- /dev/null +++ b/docs/docker使用手册.md @@ -0,0 +1,754 @@ +# Docker 使用手册 + +## 📖 目录 + +- [1. Docker 简介](#1-docker-简介) +- [2. 安装 Docker](#2-安装-docker) +- [3. Docker 基本概念](#3-docker-基本概念) +- [4. 镜像管理](#4-镜像管理) +- [5. 容器管理](#5-容器管理) +- [6. Dockerfile 构建](#6-dockerfile-构建) +- [7. Docker Compose](#7-docker-compose) +- [8. 数据管理(Volume)](#8-数据管理volume) +- [9. 网络管理](#9-网络管理) +- [10. 常用命令速查](#10-常用命令速查) +- [11. 最佳实践与注意事项](#11-最佳实践与注意事项) + +--- + +## 1. Docker 简介 + +Docker 是一个开源的应用容器引擎,让开发者可以打包应用及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 或 Windows 机器上。 + +### 核心优势 + +- **轻量级**:容器共享宿主机内核,无需虚拟化完整 OS +- **快速启动**:秒级启动,相比虚拟机分钟级启动更快 +- **环境一致**:消除"在我机器上能跑"的问题 +- **高效资源**:内存、硬盘、CPU 占用远小于虚拟机 +- **易于迁移**:一次构建,随处运行 + +--- + +## 2. 安装 Docker + +### 2.1 Ubuntu / Debian + +```bash +# 更新软件包索引 +sudo apt update + +# 安装依赖 +sudo apt install -y apt-transport-https ca-certificates curl software-properties-common + +# 添加 Docker 官方 GPG 密钥 +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - + +# 添加 Docker 源 +sudo add-apt-repository -y "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" + +# 安装 Docker +sudo apt update +sudo apt install -y docker-ce docker-ce-cli containerd.io + +# 启动并设置开机自启 +sudo systemctl enable docker +sudo systemctl start docker + +# 将当前用户加入 docker 组(免 sudo 执行) +sudo usermod -aG docker $USER +``` + +### 2.2 CentOS / RHEL + +```bash +sudo yum install -y yum-utils +sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo +sudo yum install -y docker-ce docker-ce-cli containerd.io +sudo systemctl enable docker +sudo systemctl start docker +``` + +### 2.3 macOS + +下载 [Docker Desktop for Mac](https://hub.docker.com/editions/community/docker-ce-desktop-mac/) + +### 2.4 Windows + +下载 [Docker Desktop for Windows](https://hub.docker.com/editions/community/docker-ce-desktop-windows/) + +### 2.5 验证安装 + +```bash +docker --version +docker run hello-world +``` + +--- + +## 3. Docker 基本概念 + +| 概念 | 说明 | +|------|------| +| **镜像(Image)** | 一个只读的模板,包含创建容器的说明 | +| **容器(Container)** | 镜像的运行实例,可被启动、停止、删除 | +| **仓库(Repository)** | 集中存放镜像的地方,如 Docker Hub | +| **Dockerfile** | 自动化构建镜像的脚本文件 | +| **Volume** | 持久化存储数据卷 | +| **网络(Network)** | 容器间的通信通道 | + +### 三者的关系 + +``` +Dockerfile (构建) --> Image (镜像) --> run --> Container (容器) + ↑ + Docker Hub (仓库) +``` + +--- + +## 4. 镜像管理 + +### 4.1 搜索镜像 + +```bash +docker search nginx +docker search --filter stars=1000 ubuntu +``` + +### 4.2 拉取镜像 + +```bash +docker pull nginx:latest # 拉取最新版 +docker pull ubuntu:20.04 # 拉取指定标签 +docker pull registry.cn-hangzhou.aliyuncs.com/library/nginx # 国内镜像加速 +``` + +### 4.3 查看本地镜像 + +```bash +docker images # 查看所有镜像 +docker images -q # 只显示镜像 ID +docker image ls --filter dangling=true # 查看悬空镜像 +``` + +### 4.4 删除镜像 + +```bash +docker rmi nginx:latest # 删除指定镜像 +docker rmi -f # 强制删除 +docker image prune # 删除悬空镜像 +docker image prune -a # 删除所有未使用的镜像 +``` + +### 4.5 镜像标签与导出 + +```bash +# 打标签 +docker tag ubuntu:20.04 myubuntu:v1 + +# 导出镜像为 tar 文件 +docker save -o ubuntu.tar ubuntu:20.04 + +# 从 tar 文件导入镜像 +docker load -i ubuntu.tar +``` + +### 4.6 登录与推送 + +```bash +docker login # 登录 Docker Hub +docker push username/myapp:v1 # 推送镜像 +``` + +--- + +## 5. 容器管理 + +### 5.1 创建并启动容器 + +```bash +# 基本启动 +docker run nginx + +# 后台运行(-d) +docker run -d nginx + +# 端口映射(-p 宿主机端口:容器端口) +docker run -d -p 8080:80 nginx + +# 命名容器(--name) +docker run -d --name my-nginx -p 8080:80 nginx + +# 设置环境变量(-e) +docker run -d --name my-mysql -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7 + +# 挂载数据卷(-v) +docker run -d -v /host/data:/container/data nginx + +# 自动删除(--rm,容器停止后自动删除) +docker run --rm -it ubuntu bash + +# 交互模式(-it) +docker run -it ubuntu bash +``` + +### 5.2 查看容器 + +```bash +docker ps # 查看运行中的容器 +docker ps -a # 查看所有容器(含已停止) +docker ps -q # 只显示容器 ID +docker container ls -a # 同 docker ps -a +``` + +### 5.3 停止、启动、重启容器 + +```bash +docker stop # 停止容器 +docker start # 启动已停止的容器 +docker restart # 重启容器 +docker pause # 暂停容器 +docker unpause # 恢复暂停的容器 +``` + +### 5.4 进入容器 + +```bash +# 推荐方式(exit 不会导致容器停止) +docker exec -it bash + +# 如果容器没有 bash,可以用 sh +docker exec -it sh + +# attach 方式(exit 会导致容器停止) +docker attach +``` + +### 5.5 查看日志 + +```bash +docker logs # 查看所有日志 +docker logs -f # 实时跟踪日志 +docker logs --tail 100 # 查看最近 100 行 +docker logs -t # 显示时间戳 +``` + +### 5.6 容器内部文件操作 + +```bash +# 从宿主机拷贝文件到容器 +docker cp /host/file.txt container_id:/container/path/ + +# 从容器拷贝文件到宿主机 +docker cp container_id:/container/file.txt /host/path/ +``` + +### 5.7 删除容器 + +```bash +docker rm # 删除已停止的容器 +docker rm -f # 强制删除运行中的容器 +docker container prune # 删除所有已停止的容器 +docker rm $(docker ps -aq) # 删除所有容器 +``` + +### 5.8 查看容器详细信息 + +```bash +docker inspect # 查看容器详细信息(JSON) +docker inspect --format '{{.NetworkSettings.IPAddress}}' +docker top # 查看容器中的进程 +docker stats # 查看所有容器的资源使用 +``` + +--- + +## 6. Dockerfile 构建 + +### 6.1 Dockerfile 指令详解 + +```dockerfile +# 指定基础镜像 +FROM ubuntu:20.04 + +# 维护者信息 +LABEL maintainer="yourname@example.com" + +# 设置环境变量 +ENV APP_HOME /app +ENV TZ Asia/Shanghai + +# 设置工作目录 +WORKDIR $APP_HOME + +# 运行命令(构建时执行) +RUN apt update && apt install -y python3 python3-pip nginx + +# 复制文件到镜像 +COPY ./app /app +ADD ./archive.tar.gz /tmp/ # ADD 支持自动解压 tar 和远程 URL + +# 暴露端口(仅声明,不实际映射) +EXPOSE 80 443 + +# 设置卷 +VOLUME ["/data", "/config"] + +# 运行用户 +USER appuser + +# 启动命令 +CMD ["python3", "app.py"] + +# 或使用 ENTRYPOINT(可追加参数) +ENTRYPOINT ["python3"] +CMD ["app.py"] # 作为 ENTRYPOINT 的默认参数 +``` + +### 6.2 构建镜像 + +```bash +docker build -t myapp:v1 . # 当前目录构建 +docker build -t myapp:v1 -f Dockerfile.prod . # 指定 Dockerfile +docker build --no-cache -t myapp:v1 . # 不使用缓存 +``` + +### 6.3 多阶段构建 + +```dockerfile +# 阶段一:编译 +FROM golang:1.19 AS builder +WORKDIR /app +COPY . . +RUN go build -o myapp + +# 阶段二:运行(极小镜像) +FROM alpine:latest +RUN apk add --no-cache ca-certificates +WORKDIR /root/ +COPY --from=builder /app/myapp . +EXPOSE 8080 +CMD ["./myapp"] +``` + +### 6.4 优化建议 + +```dockerfile +# ✅ 合并 RUN 命令,减少层数 +RUN apt update && \ + apt install -y curl nginx && \ + rm -rf /var/lib/apt/lists/* + +# ✅ 使用 .dockerignore 忽略不必要的文件 +# .dockerignore 文件内容: +# node_modules +# .git +# *.log +# Dockerfile +``` + +--- + +## 7. Docker Compose + +### 7.1 安装 Docker Compose + +```bash +# Linux +sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose +sudo chmod +x /usr/local/bin/docker-compose + +# Ubuntu 通过 apt +sudo apt install -y docker-compose + +# 验证 +docker-compose --version +``` + +### 7.2 docker-compose.yml 示例 + +```yaml +version: '3.8' + +services: + # Web 服务 + web: + image: nginx:latest + container_name: my-web + ports: + - "8080:80" + - "443:443" + volumes: + - ./html:/usr/share/nginx/html:ro + - ./nginx.conf:/etc/nginx/nginx.conf:ro + networks: + - app-net + depends_on: + - api + restart: always + + # API 服务 + api: + build: + context: ./api + dockerfile: Dockerfile + container_name: my-api + environment: + - DB_HOST=db + - DB_PORT=3306 + volumes: + - ./api:/app + networks: + - app-net + depends_on: + db: + condition: service_healthy + restart: unless-stopped + + # 数据库 + db: + image: mysql:5.7 + container_name: my-db + environment: + MYSQL_ROOT_PASSWORD: ${DB_PASSWORD:-root123} + MYSQL_DATABASE: myapp + volumes: + - mysql-data:/var/lib/mysql + - ./init.sql:/docker-entrypoint-initdb.d/init.sql + networks: + - app-net + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + interval: 10s + timeout: 5s + retries: 5 + restart: always + + # Redis 缓存 + redis: + image: redis:7-alpine + container_name: my-redis + ports: + - "6379:6379" + volumes: + - redis-data:/data + networks: + - app-net + restart: always + +volumes: + mysql-data: + driver: local + redis-data: + driver: local + +networks: + app-net: + driver: bridge +``` + +### 7.3 Compose 常用命令 + +```bash +# 启动所有服务(-d 后台运行) +docker-compose up -d + +# 构建并启动 +docker-compose up -d --build + +# 停止并删除容器 +docker-compose down + +# 停止并删除容器 + 数据卷 +docker-compose down -v + +# 查看日志 +docker-compose logs -f + +# 查看服务状态 +docker-compose ps + +# 重启服务 +docker-compose restart + +# 执行命令 +docker-compose exec web bash + +# 查看镜像 +docker-compose images + +# 拉取最新镜像 +docker-compose pull + +# 指定配置文件 +docker-compose -f docker-compose.prod.yml up -d +``` + +### 7.4 环境变量管理 + +```bash +# 创建 .env 文件 +echo "DB_PASSWORD=mysecret" > .env +echo "APP_ENV=production" >> .env + +# 在 compose 文件中使用 ${VAR_NAME} 引用 +# 默认值语法:${VAR_NAME:-default_value} +``` + +--- + +## 8. 数据管理(Volume) + +### 8.1 Volume 类型 + +| 类型 | 说明 | 适用场景 | +|------|------|----------| +| **命名卷** | docker 管理的卷,存储在 `/var/lib/docker/volumes/` | 持久化数据库数据 | +| **绑定挂载** | 挂载宿主机目录到容器 | 开发时热更新代码 | +| **tmpfs 挂载** | 存储在内存中 | 敏感数据,临时文件 | + +### 8.2 管理 Volume + +```bash +# 创建卷 +docker volume create my-volume + +# 查看卷 +docker volume ls +docker volume inspect my-volume + +# 删除卷 +docker volume rm my-volume +docker volume prune # 删除所有未使用的卷 + +# 使用卷运行容器 +docker run -d -v my-volume:/data nginx + +# 绑定挂载 +docker run -d -v /host/path:/container/path nginx + +# 只读挂载 +docker run -d -v /host/path:/container/path:ro nginx + +# tmpfs 挂载 +docker run -d --tmpfs /tmp:rw,noexec,nosuid,size=100m nginx +``` + +### 8.3 容器间共享数据 + +```bash +# 创建数据卷容器 +docker create -v /shared-data --name data-container ubuntu + +# 其他容器使用 --volumes-from 共享 +docker run -d --volumes-from data-container --name app1 nginx +docker run -d --volumes-from data-container --name app2 nginx +``` + +--- + +## 9. 网络管理 + +### 9.1 网络类型 + +| 网络模式 | 说明 | 适用场景 | +|----------|------|----------| +| **bridge** | 默认,通过虚拟网桥通信 | 单机容器间通信 | +| **host** | 直接使用宿主机网络 | 高性能场景 | +| **none** | 无网络 | 安全隔离 | +| **overlay** | 跨主机通信 | 多节点集群 | + +### 9.2 管理网络 + +```bash +# 创建网络 +docker network create --driver bridge my-network + +# 创建带子网的网络 +docker network create --subnet=172.20.0.0/16 --ip-range=172.20.10.0/24 my-network + +# 查看网络 +docker network ls +docker network inspect my-network + +# 连接容器到网络 +docker network connect my-network container_name + +# 断开容器网络 +docker network disconnect my-network container_name + +# 删除网络 +docker network rm my-network +docker network prune # 删除所有未使用的网络 +``` + +### 9.3 指定 IP 运行 + +```bash +docker run -d --network my-network --ip 172.20.10.10 nginx +``` + +### 9.4 容器间 DNS 解析 + +同一自定义网络下的容器,可以通过容器名直接通信: + +```bash +docker run -d --network my-network --name web nginx +docker run -d --network my-network --name app alpine ping web +``` + +--- + +## 10. 常用命令速查 + +### 10.1 镜像操作 + +| 命令 | 说明 | +|------|------| +| `docker images` | 列出镜像 | +| `docker pull ` | 拉取镜像 | +| `docker push ` | 推送镜像 | +| `docker rmi ` | 删除镜像 | +| `docker build -t .` | 构建镜像 | +| `docker tag ` | 打标签 | +| `docker save -o file.tar ` | 导出镜像 | +| `docker load -i file.tar` | 导入镜像 | +| `docker image prune` | 清理悬空镜像 | + +### 10.2 容器操作 + +| 命令 | 说明 | +|------|------| +| `docker ps -a` | 查看所有容器 | +| `docker run -d --name ` | 后台运行容器 | +| `docker exec -it bash` | 进入容器 | +| `docker logs -f ` | 查看日志 | +| `docker stop ` | 停止容器 | +| `docker start ` | 启动容器 | +| `docker restart ` | 重启容器 | +| `docker rm -f ` | 强制删除容器 | +| `docker cp :/path /local` | 拷贝文件 | +| `docker inspect ` | 查看详情 | +| `docker stats` | 查看资源占用 | +| `docker container prune` | 清理已停止容器 | + +### 10.3 系统维护 + +| 命令 | 说明 | +|------|------| +| `docker info` | Docker 系统信息 | +| `docker version` | 版本信息 | +| `docker system df` | 查看磁盘占用 | +| `docker system prune -a` | 清理所有未使用资源 | +| `docker system prune -a --volumes` | 清理全部(含卷) | + +--- + +## 11. 最佳实践与注意事项 + +### 11.1 安全建议 + +1. **不使用 root 用户运行容器**:在 Dockerfile 中使用 `USER` 指定非 root 用户 +2. **镜像最小化**:使用 alpine 等精简基础镜像 +3. **限制资源**: + ```bash + docker run -d \ + --memory="512m" \ + --cpus="1.0" \ + --memory-swap="1g" \ + --read-only \ + nginx + ``` +4. **定期扫描漏洞**:使用 `docker scan` 或 Trivy 扫描镜像 + +### 11.2 性能优化 + +1. **使用 `.dockerignore`**:避免将不必要的文件构建到镜像中 +2. **合并 RUN 指令**:减少镜像层数 +3. **利用构建缓存**:将不常变化的指令放在 Dockerfile 前面 +4. **选择合适的基础镜像**:alpine (~5MB) vs ubuntu (~200MB) + +### 11.3 日志与调试 + +```bash +# 限制容器日志大小(在 docker-compose.yml 中) +services: + app: + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + +# 全局设置(daemon.json) +# /etc/docker/daemon.json +{ + "log-driver": "json-file", + "log-opts": { + "max-size": "10m", + "max-file": "3" + } +} +``` + +### 11.4 镜像加速器配置 + +```bash +# /etc/docker/daemon.json +{ + "registry-mirrors": [ + "https://registry.docker-cn.com", + "https://docker.mirrors.ustc.edu.cn" + ] +} +# 重启 Docker +sudo systemctl restart docker +``` + +### 11.5 清理空间 + +```bash +# 一键清理(慎用!会删除未使用的镜像、容器、网络、构建缓存) +docker system prune -a + +# 只清理构建缓存 +docker builder prune + +# 查看空间占用 +docker system df +``` + +### 11.6 常见问题排查 + +```bash +# 1. 容器无法启动 -> 查看日志 +docker logs + +# 2. 端口冲突 -> 查看占用 +sudo netstat -tlnp | grep 80 + +# 3. 磁盘空间不足 -> 清理 +docker system prune -a +docker volume prune + +# 4. 容器间无法通信 -> 检查网络 +docker network inspect + +# 5. 进入排查模式 +docker run -it --rm --network container:<目标容器名> nicolaka/netshoot +``` + +--- + +## 附录:资源链接 + +- [Docker 官方文档](https://docs.docker.com/) +- [Docker Hub](https://hub.docker.com/) +- [Docker Compose 官方文档](https://docs.docker.com/compose/) +- [Dockerfile 最佳实践](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/) +- [Play With Docker(在线实验)](https://labs.play-with-docker.com/) + +--- + +*本文档由 Docker 虚拟机管理助手生成,持续更新中...* diff --git a/docs/git使用手册.md b/docs/git使用手册.md new file mode 100644 index 0000000..b89b606 --- /dev/null +++ b/docs/git使用手册.md @@ -0,0 +1,431 @@ +# Git 使用手册 + +> 版本: 1.0 +> 适用系统: Ubuntu 20.04 / 通用 +> Git 版本: 2.25.1+ + +--- + +## 目录 + +1. [Git 简介](#1-git-简介) +2. [安装与配置](#2-安装与配置) +3. [基础操作](#3-基础操作) +4. [分支管理](#4-分支管理) +5. [远程仓库](#5-远程仓库) +6. [撤销与回退](#6-撤销与回退) +7. [标签管理](#7-标签管理) +8. [常见工作流程](#8-常见工作流程) +9. [常用命令速查表](#9-常用命令速查表) + +--- + +## 1. Git 简介 + +Git 是一个**分布式版本控制系统**,由 Linus Torvalds 于 2005 年创建。它能够高效地处理从小型到大型项目的版本管理,是目前最流行的版本控制工具。 + +### 核心特点 + +- **分布式**: 每个开发者本地都有完整的代码仓库 +- **高效**: 针对文件差异和分支操作进行了优化 +- **数据完整性**: 所有数据在存储前都会计算校验和 +- **支持非线性开发**: 轻量级分支,鼓励分支开发 + +--- + +## 2. 安装与配置 + +### 2.1 在 Ubuntu/Debian 上安装 + +```bash +# 更新软件包列表 +sudo apt update + +# 安装 Git +sudo apt install -y git + +# 验证安装 +git --version +``` + +### 2.2 在 CentOS/RHEL 上安装 + +```bash +sudo yum install -y git +``` + +### 2.3 基本配置 + +```bash +# 配置用户名(提交时记录) +git config --global user.name "Your Name" + +# 配置邮箱 +git config --global user.email "your_email@example.com" + +# 配置默认分支名(Git 2.28+) +git config --global init.defaultBranch main + +# 配置默认文本编辑器 +git config --global core.editor "vim" + +# 查看当前配置 +git config --list +``` + +> 配置文件的优先级: 项目级 `.git/config` > 全局 `~/.gitconfig` > 系统级 `/etc/gitconfig` + +--- + +## 3. 基础操作 + +### 3.1 创建仓库 + +```bash +# 在当前目录初始化新仓库 +git init + +# 克隆远程仓库 +git clone <仓库地址> +git clone <仓库地址> <本地目录名> +``` + +### 3.2 文件状态 + +Git 文件有三种状态:**已修改 (modified)**、**已暂存 (staged)**、**已提交 (committed)** + +```bash +# 查看文件状态 +git status + +# 以简洁模式查看 +git status -s +``` + +状态标识说明: +- `??` - 未跟踪的文件 +- `M` - 已修改 +- `A` - 已暂存 +- `D` - 已删除 + +### 3.3 添加与提交 + +```bash +# 添加文件到暂存区 +git add <文件名> # 添加指定文件 +git add . # 添加所有变更 +git add -A # 添加所有变更(包括删除) +git add -p # 交互式分段添加 + +# 提交到本地仓库 +git commit -m "提交说明" +git commit -am "提交说明" # 跳过 git add(仅对已跟踪文件有效) + +# 修改最后一次提交 +git commit --amend -m "新的提交说明" +``` + +### 3.4 查看历史 + +```bash +# 查看提交历史 +git log +git log --oneline # 简洁模式 +git log --graph # 图形化显示 +git log --all # 显示所有分支 +git log -p # 显示差异内容 +git log --author="名字" # 按作者筛选 +git log --since="2024-01-01" # 按时间筛选 + +# 查看某个文件的修改历史 +git log -p <文件名> +git blame <文件名> # 每行最后修改者 +``` + +--- + +## 4. 分支管理 + +### 4.1 创建与切换 + +```bash +# 查看分支列表 +git branch # 本地分支 +git branch -r # 远程分支 +git branch -a # 所有分支 + +# 创建分支 +git branch <分支名> + +# 切换分支 +git checkout <分支名> +git switch <分支名> # 新式语法 + +# 创建并切换分支 +git checkout -b <分支名> +git switch -c <分支名> + +# 重命名分支 +git branch -m <旧名> <新名> +``` + +### 4.2 合并与变基 + +```bash +# 合并指定分支到当前分支 +git merge <分支名> + +# 变基操作(将当前分支的提交移至目标分支顶端) +git rebase <分支名> + +# 交互式变基(合并、修改提交记录) +git rebase -i HEAD~3 # 修改最近 3 个提交 +``` + +> **合并 vs 变基**: 合并保留完整历史,变基使历史更线性。公共分支推荐使用合并,个人分支可使用变基。 + +### 4.3 删除分支 + +```bash +# 删除本地分支 +git branch -d <分支名> # 安全删除(会检查是否已合并) +git branch -D <分支名> # 强制删除 + +# 删除远程分支 +git push origin --delete <分支名> +``` + +--- + +## 5. 远程仓库 + +### 5.1 管理远程仓库 + +```bash +# 查看远程仓库 +git remote -v + +# 添加远程仓库 +git remote add origin <仓库地址> + +# 修改远程仓库地址 +git remote set-url origin <新地址> + +# 删除远程仓库 +git remote remove origin +``` + +### 5.2 推送与拉取 + +```bash +# 推送到远程仓库 +git push origin <分支名> +git push -u origin <分支名> # 首次推送,建立关联 +git push --all # 推送所有分支 +git push origin --tags # 推送所有标签 + +# 拉取远程更新 +git pull # 拉取并合并(相当于 fetch + merge) +git pull --rebase # 拉取并变基(推荐) +git fetch # 仅拉取,不自动合并 +``` + +### 5.3 处理冲突 + +```bash +# 当合并或拉取产生冲突时: +# 1. 编辑冲突文件,保留需要的内容 +# 2. 标记为已解决 +git add <已解决冲突的文件> + +# 3. 完成合并 +git commit + +# 中止合并 +git merge --abort +``` + +--- + +## 6. 撤销与回退 + +### 6.1 撤销工作区修改 + +```bash +# 恢复文件到最近一次提交的状态 +git checkout -- <文件名> +git restore <文件名> # 新式语法 +``` + +### 6.2 撤销暂存区 + +```bash +# 将文件从暂存区移出(保留工作区修改) +git reset HEAD <文件名> +git restore --staged <文件名> # 新式语法 +``` + +### 6.3 回退提交 + +```bash +# 软重置(保留工作区和暂存区修改) +git reset --soft HEAD~1 + +# 混合重置(保留工作区,清空暂存区)- 默认模式 +git reset --mixed HEAD~1 + +# 硬重置(丢弃所有修改)- 谨慎使用! +git reset --hard HEAD~1 + +# 回退到指定提交 +git reset --hard <提交哈希> + +# 生成一个新的反向提交来撤销修改 +git revert HEAD # 撤销最近一次提交 +git revert <提交哈希> +``` + +> `git reset` 与 `git revert` 的区别:`reset` 会删除提交历史,`revert` 会保留历史并新增一个反向提交。公共分支应使用 `revert`。 + +--- + +## 7. 标签管理 + +```bash +# 创建标签 +git tag <标签名> # 轻量标签 +git tag -a <标签名> -m "说明" # 附注标签 + +# 查看标签 +git tag +git tag -l "v1.*" # 匹配模式 + +# 推送标签到远程 +git push origin <标签名> +git push origin --tags + +# 删除标签 +git tag -d <标签名> +git push origin --delete <标签名> +``` + +--- + +## 8. 常见工作流程 + +### 8.1 Git Flow 工作流 + +适用于有固定发布周期的项目: + +- `main` - 主分支,存放稳定版本 +- `develop` - 开发分支 +- `feature/*` - 功能分支 +- `release/*` - 发布分支 +- `hotfix/*` - 紧急修复分支 + +### 8.2 GitHub Flow + +适用于持续交付的项目: + +1. 从 `main` 创建功能分支 +2. 提交修改 +3. 发起 Pull Request +4. 代码评审 + 自动化测试 +5. 合并回 `main` +6. 立即部署 + +### 8.3 日常开发流程示例 + +```bash +# 1. 更新主分支 +git checkout main +git pull + +# 2. 创建功能分支 +git checkout -b feature/my-feature + +# 3. 开发并提交 +git add . +git commit -m "feat: 添加新功能" + +# 4. 同步上游变更 +git fetch origin +git rebase origin/main + +# 5. 推送并创建 Pull Request +git push -u origin feature/my-feature +``` + +--- + +## 9. 常用命令速查表 + +| 操作 | 命令 | +|------|------| +| 初始化仓库 | `git init` | +| 克隆仓库 | `git clone ` | +| 查看状态 | `git status` | +| 添加文件 | `git add ` | +| 提交 | `git commit -m "msg"` | +| 查看日志 | `git log --oneline --graph` | +| 创建分支 | `git branch ` | +| 切换分支 | `git switch ` | +| 创建+切换 | `git switch -c ` | +| 合并分支 | `git merge ` | +| 推送 | `git push origin ` | +| 拉取 | `git pull --rebase` | +| 查看差异 | `git diff` | +| 暂存修改 | `git stash` | +| 恢复暂存 | `git stash pop` | +| 查看远程 | `git remote -v` | + +--- + +## 附录 + +### .gitignore 文件示例 + +```gitignore +# 编译输出 +*.o +*.class +*.exe +*.dll + +# 依赖目录 +node_modules/ +vendor/ +__pycache__/ + +# IDE 配置 +.vscode/ +.idea/ +*.swp + +# 系统文件 +.DS_Store +Thumbs.db + +# 环境配置 +.env +*.local +``` + +### 提交信息规范 + +推荐使用 [Conventional Commits](https://www.conventionalcommits.org/) 规范: + +``` +<类型>(<范围>): <简短描述> + +<详细描述> + +<尾部信息> +``` + +类型包括:`feat`、`fix`、`docs`、`style`、`refactor`、`test`、`chore` 等。 + +--- + +> 本文档持续更新中... +> 更多信息请访问 [Git 官方文档](https://git-scm.com/doc) diff --git a/docs/images/gui.png b/docs/images/gui.png new file mode 100644 index 0000000000000000000000000000000000000000..45c3373d001be26d53f63b6aaac2b0bd94dae814 GIT binary patch literal 67161 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 Yq4qQ3yLp#>bxgW)(fmSzk;Bvf0n!zaKmY&$ literal 0 HcmV?d00001 diff --git a/docs/nvm使用文档.md b/docs/nvm使用文档.md new file mode 100644 index 0000000..a24ac0f --- /dev/null +++ b/docs/nvm使用文档.md @@ -0,0 +1,189 @@ +# NVM (Node Version Manager) 使用文档 + +## 一、什么是 NVM? + +NVM(Node Version Manager)是一个用于管理多个 Node.js 版本的工具。它可以让你在同一台机器上安装、切换和使用不同版本的 Node.js,非常适合需要兼顾不同项目 Node.js 版本需求的开发者。 + +--- + +## 二、安装 NVM + +### 2.1 一键安装 + +```bash +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash +``` + +### 2.2 加载 NVM + +安装完成后,需要重新加载终端或者执行以下命令使 nvm 生效: + +```bash +export NVM_DIR="$HOME/.nvm" +[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" +[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" +``` + +### 2.3 验证安装 + +```bash +nvm --version +``` + +--- + +## 三、配置镜像源(加速 Node.js 下载) + +在国内使用 nvm 下载 Node.js 时,建议配置淘宝镜像源以提高下载速度。 + +### 临时配置(仅当前会话生效) + +```bash +export NVM_NODEJS_ORG_MIRROR=https://npmmirror.com/mirrors/node +``` + +### 永久配置(写入 shell 配置文件) + +```bash +echo 'export NVM_NODEJS_ORG_MIRROR=https://npmmirror.com/mirrors/node' >> ~/.bashrc +source ~/.bashrc +``` + +--- + +## 四、NVM 常用命令 + +### 4.1 安装 Node.js + +```bash +# 安装最新稳定版 +nvm install node + +# 安装指定版本 +nvm install 20.11.0 + +# 安装最新的 LTS 版本 +nvm install --lts +``` + +### 4.2 查看可安装的版本 + +```bash +# 列出所有可安装的版本 +nvm ls-remote + +# 列出所有 LTS 版本 +nvm ls-remote --lts +``` + +### 4.3 切换 Node.js 版本 + +```bash +# 使用指定版本 +nvm use 20.11.0 + +# 使用最新稳定版 +nvm use node + +# 使用最新的 LTS 版本 +nvm use --lts +``` + +### 4.4 查看已安装的版本 + +```bash +nvm ls +``` + +### 4.5 设置默认版本 + +```bash +# 设置系统默认使用的 Node.js 版本 +nvm alias default 20.11.0 + +# 将最新稳定版设为默认 +nvm alias default node +``` + +### 4.6 卸载 Node.js 版本 + +```bash +nvm uninstall 20.11.0 +``` + +--- + +## 五、版本管理实践 + +### 5.1 为项目指定 Node.js 版本 + +在项目根目录创建 `.nvmrc` 文件: + +```bash +# .nvmrc 文件内容示例 +20.11.0 +``` + +然后在该目录下运行 `nvm use` 即可自动切换到对应版本。 + +### 5.2 查看当前使用的版本 + +```bash +node -v +npm -v +``` + +### 5.3 版本别名管理 + +```bash +# 创建别名 +nvm alias myapp 20.11.0 + +# 使用别名切换 +nvm use myapp + +# 删除别名 +nvm unalias myapp +``` + +--- + +## 六、常见问题 + +### 6.1 命令找不到(command not found) + +如果重启终端后出现 `nvm: command not found`,请确保已将 nvm 加载命令添加到 `~/.bashrc`、`~/.zshrc` 或 `~/.profile` 文件中。 + +### 6.2 下载速度慢 + +配置淘宝镜像源即可解决: + +```bash +export NVM_NODEJS_ORG_MIRROR=https://npmmirror.com/mirrors/node +``` + +### 6.3 权限问题 + +使用 nvm 安装的 Node.js 不需要 `sudo` 权限,如果遇到权限问题,请检查是否使用了系统自带的 Node.js。 + +--- + +## 七、常用命令速查表 + +| 命令 | 说明 | +|------|------| +| `nvm --version` | 查看 nvm 版本 | +| `nvm install ` | 安装指定版本 | +| `nvm install node` | 安装最新稳定版 | +| `nvm install --lts` | 安装最新 LTS 版本 | +| `nvm use ` | 切换使用指定版本 | +| `nvm ls` | 列出已安装版本 | +| `nvm ls-remote` | 列出远程所有版本 | +| `nvm alias default ` | 设置默认版本 | +| `nvm uninstall ` | 卸载指定版本 | +| `nvm current` | 显示当前使用的版本 | + +--- + +> 文档版本: v1.0 +> 最后更新: 2025年 diff --git a/docs/uv使用文档.md b/docs/uv使用文档.md new file mode 100644 index 0000000..7e62a22 --- /dev/null +++ b/docs/uv使用文档.md @@ -0,0 +1,424 @@ +# uv 使用文档 + +> uv 是一个用 Rust 编写的极速 Python 包管理与工具链管理器,由 Astral 公司(Ruff 作者)开发。 +> 它可替代 pip、pip-tools、pipx、poetry、pyenv、virtualenv 等多个工具。 + +--- + +## 目录 + +1. [简介](#1-简介) +2. [安装与配置](#2-安装与配置) +3. [核心命令速查](#3-核心命令速查) +4. [Python 版本管理](#4-python-版本管理) +5. [虚拟环境管理](#5-虚拟环境管理) +6. [包管理](#6-包管理) +7. [项目工作流](#7-项目工作流) +8. [镜像源配置](#8-镜像源配置) +9. [常见问题](#9-常见问题) + +--- + +## 1. 简介 + +### 1.1 什么是 uv? + +uv 是一个"大一统"的 Python 工具链,具备以下核心能力: + +| 功能 | 对应传统工具 | +|------|------------| +| Python 版本安装与管理 | pyenv | +| 虚拟环境创建 | venv / virtualenv | +| 包安装 | pip | +| 依赖锁定 | pip-tools / poetry | +| 工具包运行 | pipx | +| 脚本依赖管理 | 原生支持 | + +### 1.2 核心优势 + +- **极快速度**:比 pip 快 10-100 倍 +- **单一二进制**:无需 Python 即可安装 uv +- **兼容性强**:兼容 pip 的工作流和 requirements.txt +- **全面替代**:一个工具解决 Python 环境所有问题 + +--- + +## 2. 安装与配置 + +### 2.1 一键安装(推荐) + +```bash +curl -LsSf https://astral.sh/uv/install.sh | sh +``` + +安装后重启终端或执行: + +```bash +source $HOME/.local/bin/env +``` + +### 2.2 其他安装方式 + +**macOS (Homebrew):** +```bash +brew install uv +``` + +**Linux (apt/手动):** +```bash +# 下载二进制 +curl -LsSf https://astral.sh/uv/install.sh | sh +``` + +**Windows (PowerShell):** +```powershell +powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" +``` + +### 2.3 验证安装 + +```bash +uv --version +# 输出示例: uv 0.11.11 (x86_64-unknown-linux-gnu) +``` + +### 2.4 配置国内镜像源(加速) + +创建配置文件 `~/.config/uv/uv.toml`: + +```toml +# Python 版本下载镜像源 +python-install-mirror = "https://registry.npmmirror.com/-/binary/python-build-standalone/" + +# pip 包下载镜像源(阿里云) +[[index]] +url = "https://mirrors.aliyun.com/pypi/simple/" +default = true +``` + +也可通过环境变量临时设置: + +```bash +export UV_PYTHON_INSTALL_MIRROR="https://registry.npmmirror.com/-/binary/python-build-standalone/" +``` + +--- + +## 3. 核心命令速查 + +| 命令 | 说明 | +|------|------| +| `uv python install <版本>` | 安装指定 Python 版本 | +| `uv python list` | 列出可用的 Python 版本 | +| `uv venv` | 创建虚拟环境 | +| `uv pip install <包>` | 安装 Python 包 | +| `uv pip sync requirements.txt` | 同步依赖 | +| `uv run <文件>` | 在虚拟环境中运行脚本 | +| `uv add <包>` | 向项目添加依赖 | +| `uv lock` | 生成锁定文件 | +| `uv tool run <工具>` | 运行工具包(类似 pipx) | +| `uv init` | 初始化新项目 | +| `uv build` | 构建分发包 | + +--- + +## 4. Python 版本管理 + +### 4.1 查看可安装的 Python 版本 + +```bash +# 列出所有可下载的版本 +uv python list --only-downloads + +# 列出已安装的版本 +uv python list +``` + +### 4.2 安装指定 Python 版本 + +```bash +# 安装最新 Python 3.12 +uv python install 3.12 + +# 安装具体版本 +uv python install 3.11.11 + +# 安装多个版本 +uv python install 3.10 3.11 3.12 +``` + +### 4.3 查看与切换当前 Python + +```bash +# 查看当前使用的 Python +uv python + +# 查找特定版本的路径 +uv python find 3.11 + +# 为项目固定 Python 版本 +uv python pin 3.12 +``` + +### 4.4 卸载 Python 版本 + +```bash +# uv 安装的 Python 存储在 ~/.local/share/uv/python/ +# 直接删除对应目录即可 +rm -rf ~/.local/share/uv/python/cpython-3.10* +``` + +--- + +## 5. 虚拟环境管理 + +### 5.1 创建虚拟环境 + +```bash +# 在当前目录创建 .venv(使用默认 Python) +uv venv + +# 指定 Python 版本创建 +uv venv --python 3.12 + +# 指定名称 +uv venv myenv +``` + +### 5.2 激活虚拟环境 + +```bash +# Linux / macOS +source .venv/bin/activate + +# Windows +.venv\Scripts\activate +``` + +> **提示**:uv 会自动检测并使用当前目录下的 `.venv`,无需手动激活也可通过 `uv run` 使用。 + +### 5.3 删除虚拟环境 + +```bash +# 直接删除 .venv 目录即可 +rm -rf .venv +``` + +--- + +## 6. 包管理 + +uv 兼容 pip 的全部功能,且速度更快。 + +### 6.1 安装包 + +```bash +# 安装单个包 +uv pip install requests + +# 安装指定版本 +uv pip install "flask>=2.0" + +# 从 requirements.txt 安装 +uv pip install -r requirements.txt + +# 从 pyproject.toml 安装 +uv pip install -e . +``` + +### 6.2 管理已安装的包 + +```bash +# 列出已安装的包 +uv pip list + +# 查看包信息 +uv pip show requests + +# 检查依赖冲突 +uv pip check + +# 卸载包 +uv pip uninstall requests +``` + +### 6.3 依赖锁定 + +```bash +# 根据 requirements.in 编译出锁定文件 +uv pip compile requirements.in -o requirements.txt + +# 同步环境到锁定状态 +uv pip sync requirements.txt +``` + +--- + +## 7. 项目工作流 + +### 7.1 初始化新项目 + +```bash +# 创建新项目 +uv init my_project +cd my_project + +# 添加依赖 +uv add requests flask + +# 安装所有依赖 +uv sync + +# 运行项目 +uv run python main.py +``` + +### 7.2 脚本依赖管理(Shebang 方式) + +在 Python 脚本顶部添加内联元数据,自动管理依赖: + +```python +# /// script +# dependencies = [ +# "requests>=2.28", +# "rich>=13.0", +# ] +# /// +import requests +from rich import print + +response = requests.get("https://api.github.com") +print(response.status_code) +``` + +运行方式: +```bash +uv run script.py +``` + +> uv 会自动创建隔离环境并安装依赖,无需手动管理! + +### 7.3 工具包管理(替代 pipx) + +```bash +# 安装工具包 +uv tool install ruff + +# 运行工具(无需安装) +uv tool run black --help + +# 列出已安装的工具 +uv tool list + +# 升级工具 +uv tool upgrade ruff + +# 卸载工具 +uv tool uninstall ruff +``` + +--- + +## 8. 镜像源配置 + +### 8.1 配置文件方式 + +编辑 `~/.config/uv/uv.toml`: + +```toml +# ========== Python 版本下载镜像 ========== +python-install-mirror = "https://registry.npmmirror.com/-/binary/python-build-standalone/" + +# ========== 包下载镜像源 ========== +[[index]] +url = "https://mirrors.aliyun.com/pypi/simple/" +default = true + +# 可配置多个备用镜像源 +[[index]] +url = "https://pypi.tuna.tsinghua.edu.cn/simple/" +``` + +### 8.2 环境变量方式 + +```bash +# Python 版本下载镜像 +export UV_PYTHON_INSTALL_MIRROR="https://registry.npmmirror.com/-/binary/python-build-standalone/" + +# 包下载镜像(临时覆盖) +export UV_INDEX_URL="https://mirrors.aliyun.com/pypi/simple/" +``` + +### 8.3 常用国内镜像源 + +| 镜像源 | Python 版本镜像 URL | PyPI 镜像 URL | +|--------|-------------------|--------------| +| 华为云 | `https://repo.huaweicloud.com/python` | `https://mirrors.huaweicloud.com/repository/pypi/simple` | +| 阿里云 | - | `https://mirrors.aliyun.com/pypi/simple/` | +| 清华大学 | `https://mirrors.tuna.tsinghua.edu.cn/python` | `https://pypi.tuna.tsinghua.edu.cn/simple/` | +| 中科大 | `https://mirrors.ustc.edu.cn/python` | `https://pypi.mirrors.ustc.edu.cn/simple/` | + +--- + +## 9. 常见问题 + +### Q1: 安装时遇到权限问题? + +```bash +# 确保 ~/.local/bin 在 PATH 中 +export PATH="$HOME/.local/bin:$PATH" +``` + +### Q2: 如何更新 uv? + +```bash +uv self update +``` + +### Q3: uv 和 pip 能否共存? + +完全可以。uv 兼容 pip 的工作流,两者可共存使用。建议逐步迁移到 uv。 + +### Q4: 如何查看帮助? + +```bash +# 通用帮助 +uv help + +# 子命令帮助 +uv python --help +uv pip --help +``` + +### Q5: 如何卸载 uv? + +```bash +# 删除二进制文件 +rm ~/.local/bin/uv ~/.local/bin/uvx + +# 删除配置 +rm -rf ~/.config/uv + +# 删除缓存 +rm -rf ~/.cache/uv + +# 删除安装的 Python 版本 +rm -rf ~/.local/share/uv +``` + +### Q6: 为什么我执行 `uv python install` 下载很慢? + +请检查是否已配置国内镜像源(参见第 8 节),或手动设置: + +```bash +export UV_PYTHON_INSTALL_MIRROR="https://registry.npmmirror.com/-/binary/python-build-standalone/" +``` + +--- + +## 参考资源 + +- [uv 官方文档](https://docs.astral.sh/uv/) +- [uv GitHub 仓库](https://github.com/astral-sh/uv) +- [华为云 Python 镜像](https://repo.huaweicloud.com/python) +- [阿里云 PyPI 镜像](https://mirrors.aliyun.com/pypi/simple/) diff --git a/gui.spec b/gui.spec new file mode 100644 index 0000000..d11a1d4 --- /dev/null +++ b/gui.spec @@ -0,0 +1,55 @@ +# -*- mode: python ; coding: utf-8 -*- + +import os +import sys + +# Python 的 base_prefix 路径 +python_dir = sys.base_prefix # e.g., C:\Users\chakcy\AppData\Roaming\uv\python\cpython-3.14-windows-x86_64-none + +# DLL 目录 +dlls_dir = os.path.join(python_dir, "DLLs") + +# 收集 SSL 相关的二进制文件 +ssl_binaries = [] +for f in ["_ssl.pyd", "libcrypto-3-x64.dll", "libssl-3-x64.dll"]: + src = os.path.join(dlls_dir, f) + if os.path.exists(src): + ssl_binaries.append((src, ".")) + print(f"Found: {src}") + +a = Analysis( + ['src\\virtualbox_client\\gui.py'], + pathex=[], + binaries=ssl_binaries, + datas=[], + hiddenimports=["ssl", "pywin32", "win32api", "win32security"], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name='VirtualBoxClient', + icon=['chongming.ico'], + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..5ac25c0 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,32 @@ +[build-system] +build-backend = "hatchling.build" +requires = ["hatchling"] + +[project] +name = "virtualbox-client" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.14" +dependencies = [ + "openai>=2.35.1", + "paramiko>=4.0.0", + "python-dotenv>=1.2.2", + "pywin32>=311", + "PySide6>=6.8.0", +] + +[dependency-groups] +dev = [ + "pyinstaller>=6.20.0", + "setuptools>=82.0.1", +] + +[tool.hatch.build.targets.wheel] +packages = ["src/virtualbox_client"] + +[project.scripts] +example = "virtualbox_client.scripts.example:run" +agent-task = "virtualbox_client.scripts.agent_task:run" +run-script = "virtualbox_client.scripts.run_script:run" +gui = "virtualbox_client.gui:run" diff --git a/resources/chongming_dev.sh b/resources/chongming_dev.sh new file mode 100644 index 0000000..8b34384 --- /dev/null +++ b/resources/chongming_dev.sh @@ -0,0 +1,116 @@ +#!/bin/bash +# chongming 开发环境一键启动脚本 (远程执行版) +# 用法: 上传到虚拟机 /tmp/ 后执行,会自动定位到项目根目录 +# 项目路径: ~/workspace/chongming + +set -e + +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' # No Color + +echo -e "${GREEN}==> 初始化 chongming 开发环境${NC}" + +# 1. 定位项目根目录 +PROJECT_DIR=~/workspace/chongming +if [ ! -d "$PROJECT_DIR" ]; then + echo -e "${RED}错误:项目目录不存在: $PROJECT_DIR${NC}" + echo -e "${YELLOW}请先在虚拟机中创建项目目录并拉取代码:${NC}" + echo " mkdir -p ~/workspace && cd ~/workspace" + echo " git clone <仓库地址> chongming" + exit 1 +fi + +cd "$PROJECT_DIR" +echo -e "${GREEN}✔ 切换到项目目录: $(pwd)${NC}" + +# 确认 pyproject.toml 存在(防御性检查) +if [ ! -f "pyproject.toml" ]; then + echo -e "${RED}错误:项目目录中未找到 pyproject.toml:$(pwd)${NC}" + exit 1 +fi + +# 2. 加载 nvm(SSH 非交互式 shell 需要手动加载) +export NVM_DIR="$HOME/.nvm" +if [ -s "$NVM_DIR/nvm.sh" ]; then + . "$NVM_DIR/nvm.sh" 2>/dev/null +fi + +# 3. 检查 Node.js (版本要求 >=20.19) +if ! command -v node &> /dev/null; then + echo -e "${RED}错误:未找到 node,请安装 Node.js >= 20.19${NC}" + echo "推荐使用 nvm: curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash" + exit 1 +fi +NODE_VERSION=$(node -v | cut -d'v' -f2 | cut -d'.' -f1) +if [ "$NODE_VERSION" -lt 20 ]; then + echo -e "${RED}错误:Node.js 版本过低 (当前 $(node -v)),需要 >=20.19${NC}" + exit 1 +fi +echo -e "${GREEN}✔ Node.js $(node -v)${NC}" + +# 4. 安装/检查 uv (项目推荐) +# SSH 非交互式 shell 的 PATH 可能不含 ~/.local/bin,手动添加 +export PATH="$HOME/.local/bin:$PATH" +if ! command -v uv &> /dev/null; then + echo -e "${YELLOW}未找到 uv,正在自动安装...${NC}" + curl -LsSf https://astral.sh/uv/install.sh | sh + export PATH="$HOME/.local/bin:$PATH" + if ! command -v uv &> /dev/null; then + echo -e "${RED}uv 安装失败,请手动安装:https://docs.astral.sh/uv/${NC}" + exit 1 + fi + echo -e "${GREEN}uv 安装成功${NC}" +fi + +# 5. 安装后端依赖 +echo -e "${GREEN}==> 安装后端依赖 (uv sync)${NC}" +cd ~/workspace/chongming +uv sync + +# 6. 安装前端依赖 +echo -e "${GREEN}==> 安装前端依赖 (npm install)${NC}" +cd ~/workspace/chongming/src/chongming-web +npm install +cd ../.. + +# 7. 启动后端服务(后台运行,使用 nohup 防止 SSH 断开后被 SIGHUP 杀死) +echo -e "${GREEN}==> 启动后端服务 (uv run serve)${NC}" +mkdir -p ~/workspace/chongming/logs +nohup uv run serve > ~/workspace/chongming/logs/backend.log 2>&1 & +BACKEND_PID=$! + +# 等待后端完全启动(健康检查) +echo "等待后端服务就绪(约3秒)..." +sleep 10 +# 可选:检查端口8000是否监听 +if ! ss -tln | grep -q ':8000'; then + echo -e "${YELLOW}警告:后端可能尚未完全启动,请稍后手动检查${NC}" +fi + +# 8. 生成前端 API 客户端(需要后端运行) +echo -e "${GREEN}==> 生成前端 API 客户端 (npm run openapi)${NC}" +cd src/chongming-web +npm run openapi || echo -e "${YELLOW}注意:生成 openapi 失败,请确认后端已运行在 http://localhost:8000${NC}" +cd ../.. + +# 9. 启动前端开发服务器(后台运行,使用 nohup 防止 SSH 断开后被 SIGHUP 杀死) +echo -e "${GREEN}==> 启动前端开发服务器 (npm run dev)${NC}" +cd src/chongming-web +nohup npm run dev > ~/workspace/chongming/logs/frontend.log 2>&1 & +FRONTEND_PID=$! +cd ../.. + +echo -e "前端地址: ${GREEN}http://localhost:5173${NC}" +echo -e "后端地址: ${GREEN}http://localhost:8000${NC}" +echo -e "后端日志: ~/workspace/chongming/logs/backend.log" +echo -e "前端日志: ~/workspace/chongming/logs/frontend.log" +echo -e "后端 PID: $BACKEND_PID" +echo -e "前端 PID: $FRONTEND_PID" + +# 记录 PID 到文件,方便后续停止服务 +echo -e "$BACKEND_PID\n$FRONTEND_PID" > ~/workspace/chongming/.dev_pids + +echo -e "${GREEN}✅ 开发环境已全部启动,脚本将退出,服务在后台持续运行${NC}" +echo -e "${YELLOW}要停止服务,请执行: kill \$(cat ~/workspace/chongming/.dev_pids)${NC}" diff --git a/resources/deploy_docker.sh b/resources/deploy_docker.sh new file mode 100644 index 0000000..a06c5a4 --- /dev/null +++ b/resources/deploy_docker.sh @@ -0,0 +1,189 @@ +#!/bin/bash +# +# deploy_docker.sh - Docker 一键部署脚本 +# 适用系统: Ubuntu 20.04 (Focal) +# 功能: 安装 Docker Engine 并配置国内镜像加速器 +# + +set -e + +# 颜色输出 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +log_info() { echo -e "${GREEN}[INFO]${NC} $1"; } +log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +log_error() { echo -e "${RED}[ERROR]${NC} $1"; } + +# 检查是否以 root 运行 +check_root() { + if [ "$EUID" -ne 0 ]; then + log_warn "未以 root 身份运行,将使用 sudo 提权" + SUDO="sudo" + else + SUDO="" + fi +} + +# 步骤1: 清理旧 Docker 仓库配置(避免残余配置干扰) +cleanup_old_repo() { + log_info "步骤1: 清理旧 Docker 仓库配置..." + # 移除所有旧的 docker 源文件 + $SUDO rm -f /etc/apt/sources.list.d/docker.list /etc/apt/sources.list.d/docker*.list + # 移除旧的 docker gpg 密钥 + $SUDO rm -f /usr/share/keyrings/docker-archive-keyring.gpg + $SUDO apt-key del "7EA0A9C3F273FCD8" 2>/dev/null || true + log_info "旧 Docker 仓库配置已清理" +} + +# 步骤2: 添加 Docker 官方 GPG 密钥(使用阿里云镜像) +add_gpg_key() { + log_info "步骤2: 添加 Docker GPG 密钥..." + curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg -o /tmp/docker.gpg + # 安装到 keyrings 目录(供 signed-by 使用) + $SUDO mkdir -p /usr/share/keyrings + $SUDO gpg --batch --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg /tmp/docker.gpg 2>/dev/null || true + # 用 apt-key 添加(传统方式兼容) + $SUDO apt-key add /tmp/docker.gpg + rm -f /tmp/docker.gpg + log_info "GPG 密钥添加完成" +} + +# 步骤3: 添加 Docker APT 源(使用阿里云镜像,带 signed-by 引用) +add_docker_repo() { + log_info "步骤3: 添加 Docker APT 源..." + # 使用 signed-by 添加(指定密钥文件路径,避免 NO_PUBKEY 错误) + echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable" | $SUDO tee /etc/apt/sources.list.d/docker.list > /dev/null + log_info "Docker APT 源添加完成" +} + +# 步骤4: 更新 apt 并安装依赖(此时 Docker 源已配置好密钥) +install_dependencies() { + log_info "步骤4: 更新 apt 包索引并安装依赖..." + $SUDO apt update -y + $SUDO apt install -y \ + apt-transport-https \ + ca-certificates \ + curl \ + gnupg \ + lsb-release + log_info "依赖安装完成" +} + +# 步骤5: 安装 Docker Engine +install_docker() { + log_info "步骤5: 安装 Docker Engine(先装核心组件,再试可选组件)..." + + # 核心组件(必须成功) + $SUDO apt install -y docker-ce docker-ce-cli containerd.io || { + log_error "Docker 核心组件安装失败!" + exit 1 + } + + # 可选组件(失败不影响核心功能) + for pkg in docker-buildx-plugin docker-compose-plugin docker-ce-rootless-extras; do + if $SUDO apt install -y "$pkg" 2>/dev/null; then + log_info "✔ $pkg 安装成功" + else + log_warn "$pkg 安装失败(可选组件,可跳过)" + fi + done + + log_info "Docker Engine 安装完成" +} + +# 步骤6: 配置 Docker 镜像加速器 +configure_mirrors() { + log_info "步骤6: 配置 Docker 镜像加速器..." + $SUDO mkdir -p /etc/docker + cat > /tmp/daemon.json << 'EOF' +{ + "registry-mirrors": [ + "https://docker.m.daocloud.io", + "https://dockerproxy.com", + "https://docker.nju.edu.cn", + "https://docker.mirrors.ustc.edu.cn" + ] +} +EOF + $SUDO cp /tmp/daemon.json /etc/docker/daemon.json + rm -f /tmp/daemon.json + log_info "镜像加速器配置完成" +} + +# 步骤7: 重启 Docker 服务 +restart_docker() { + log_info "步骤7: 重启 Docker 服务..." + $SUDO systemctl restart docker + log_info "Docker 服务已重启" +} + +# 步骤8: 验证安装 +verify_installation() { + log_info "步骤8: 验证安装..." + + # 检查 Docker 版本 + if docker --version &>/dev/null; then + log_info "$(docker --version)" + else + log_error "Docker 未正确安装" + exit 1 + fi + + # 检查 Docker 服务状态 + if $SUDO systemctl is-active docker &>/dev/null; then + log_info "Docker 服务运行正常" + else + log_error "Docker 服务未运行" + exit 1 + fi + + # 检查镜像加速器 + if $SUDO docker info 2>/dev/null | grep -q "Registry Mirrors"; then + log_info "镜像加速器配置如下:" + $SUDO docker info 2>/dev/null | grep -A5 "Registry Mirrors" + fi + + # 运行 hello-world 测试 + log_info "运行 hello-world 测试..." + if $SUDO docker run --rm hello-world &>/dev/null; then + log_info "Docker 安装验证通过!" + else + log_warn "hello-world 测试未通过,请检查网络连接" + fi +} + +# 主函数 +main() { + echo "==========================================" + echo " Docker 一键部署脚本" + echo " 适用系统: Ubuntu 20.04" + echo "==========================================" + echo "" + + check_root + cleanup_old_repo + add_gpg_key + add_docker_repo + install_dependencies + install_docker + configure_mirrors + restart_docker + verify_installation + + echo "" + echo "==========================================" + echo -e "${GREEN} Docker 部署完成!${NC}" + echo "==========================================" + echo "" + echo "常用命令:" + echo " docker ps - 查看运行中的容器" + echo " docker images - 查看本地镜像" + echo " docker info - 查看 Docker 信息" + echo " docker run hello-world - 测试 Docker 安装" + echo "" +} + +main "$@" diff --git a/resources/deploy_git.sh b/resources/deploy_git.sh new file mode 100644 index 0000000..634ec0f --- /dev/null +++ b/resources/deploy_git.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# ============================================ +# deploy_git.sh - Git 安装部署脚本 +# 适用系统: Ubuntu 20.04 +# 功能: 一键安装 Git 并验证 +# ============================================ + +set -e # 遇到错误立即退出 + +echo "==========================================" +echo " 开始部署 Git ..." +echo "==========================================" + +# 步骤 1: 更新软件包列表 +echo "[Step 1/4] 更新软件包列表..." +sudo apt update -y + +# 步骤 2: 安装 Git +echo "[Step 2/4] 安装 Git..." +sudo apt install -y git + +# 步骤 3: 验证安装 +echo "[Step 3/4] 验证 Git 安装..." +if command -v git &>/dev/null; then + echo " Git 安装成功!" + git --version +else + echo " Git 安装失败!" + exit 1 +fi + +# 步骤 4: 配置基本信息(可选) +echo "[Step 4/4] 配置 Git 基本信息(可选)..." +echo " 提示:您可以使用以下命令配置用户名和邮箱:" +echo " git config --global user.name \"Your Name\"" +echo " git config --global user.email \"your_email@example.com\"" + +echo "" +echo "==========================================" +echo " Git 部署完成!" +echo " 版本: $(git --version 2>/dev/null || echo 'unknown')" +echo "==========================================" diff --git a/resources/deploy_nvm.sh b/resources/deploy_nvm.sh new file mode 100644 index 0000000..2b87f3a --- /dev/null +++ b/resources/deploy_nvm.sh @@ -0,0 +1,133 @@ +#!/bin/bash +# ============================================================== +# NVM 部署脚本 - 一键安装 nvm + Node.js LTS 稳定版 + 换源加速 +# ============================================================== +# 适用系统: Ubuntu 20.04+ +# 说明: 使用国内镜像源加速,避免 GitHub raw 下载缓慢 +# 安装 Node.js LTS 版(比 latest 更小更快) +# +# 注意:脚本可能通过 sudo 以 root 身份执行, +# 因此需要检测原始用户并切换到该用户安装 nvm, +# 避免 nvm 装到 /root 下导致原用户看不到。 +# ============================================================= + +set -e + +echo "========================================" +echo " NVM 部署脚本 - 开始安装..." +echo "========================================" + +# ---------- 检测原始用户 ---------- +# 如果通过 sudo 运行,SUDO_USER 会保留原用户名 +if [ -n "$SUDO_USER" ]; then + REAL_USER="$SUDO_USER" + REAL_HOME=$(getent passwd "$REAL_USER" | cut -d: -f6) + if [ -z "$REAL_HOME" ]; then + REAL_HOME="/home/$REAL_USER" + fi +else + REAL_USER=$(whoami) + REAL_HOME="$HOME" +fi + +echo " 检测到实际用户: $REAL_USER" +echo " 用户目录: $REAL_HOME" +echo "" + +NVM_VERSION="v0.39.7" +NODE_LTS_VERSION="--lts" # 安装 LTS 最新版(比 install node 的 latest 版更小) + +# ---------- 辅助函数:以原始用户身份执行命令 ---------- +run_as_user() { + if [ "$(whoami)" != "$REAL_USER" ]; then + sudo -u "$REAL_USER" bash -c "$*" + else + bash -c "$*" + fi +} + +# 1. 安装 nvm(使用 gitee 镜像加速) +echo "[1/4] 正在安装 nvm ${NVM_VERSION}..." +# 优先用 gitee 镜像(国内快),失败则回滚到 GitHub 官方 +NVM_INSTALL_URL="https://gitee.com/mirrors/nvm/raw/${NVM_VERSION}/install.sh" +echo " 下载源: ${NVM_INSTALL_URL}" + +# 以原始用户身份下载并执行安装脚本 +rm -f /tmp/install_nvm.sh +if timeout 30 curl -fsSL "${NVM_INSTALL_URL}" -o /tmp/install_nvm.sh 2>/dev/null; then + echo " ✅ 从 Gitee 镜像下载成功" +else + echo " ⚠ Gitee 镜像下载失败,切换到 GitHub 源..." + NVM_INSTALL_URL="https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_VERSION}/install.sh" + curl -fsSL "${NVM_INSTALL_URL}" -o /tmp/install_nvm.sh +fi + +# 以原始用户身份执行安装(不要用 sudo) +chown "$REAL_USER":"$REAL_USER" /tmp/install_nvm.sh 2>/dev/null || true +sudo -u "$REAL_USER" bash /tmp/install_nvm.sh +rm -f /tmp/install_nvm.sh + +# 2. 加载 nvm 到当前 shell(以原始用户身份) +echo "[2/4] 正在加载 nvm 环境变量..." +export NVM_DIR="$REAL_HOME/.nvm" +if [ -s "$NVM_DIR/nvm.sh" ]; then + \. "$NVM_DIR/nvm.sh" + echo " nvm 已加载" +else + echo " ⚠ nvm.sh 未找到,可能安装过程中出现问题" + ls -la "$NVM_DIR" 2>/dev/null || echo " .nvm 目录不存在" +fi + +# 3. 配置淘宝镜像源(加速 Node.js 下载) +echo "[3/4] 正在配置淘宝镜像源..." +echo 'export NVM_NODEJS_ORG_MIRROR=https://npmmirror.com/mirrors/node' >> "$REAL_HOME/.bashrc" +export NVM_NODEJS_ORG_MIRROR=https://npmmirror.com/mirrors/node +echo " 镜像源已配置: NVM_NODEJS_ORG_MIRROR=https://npmmirror.com/mirrors/node" + +# 4. 安装 Node.js LTS 版 +echo "[4/4] 正在安装 Node.js LTS 版本(稳定版)..." +echo " 下载源: ${NVM_NODEJS_ORG_MIRROR}" +# nvm install 必须以原始用户身份执行,否则 $NVM_DIR 不匹配 +sudo -u "$REAL_USER" bash -c " +export NVM_DIR='$REAL_HOME/.nvm' +[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" +export NVM_NODEJS_ORG_MIRROR='$NVM_NODEJS_ORG_MIRROR' +nvm install $NODE_LTS_VERSION +" + +# 验证安装结果 +echo "" +echo "========================================" +echo " 安装完成!验证信息如下:" +echo "========================================" + +# 以原始用户身份验证 +sudo -u "$REAL_USER" bash -c " +export NVM_DIR='$REAL_HOME/.nvm' +[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" +if command -v nvm &> /dev/null; then + echo ' nvm 版本 : \$(nvm --version)' +else + echo ' nvm 版本 : n/a' +fi +if command -v node &> /dev/null; then + echo ' node 版本: \$(node -v)' +else + echo ' node 版本: n/a' +fi +if command -v npm &> /dev/null; then + echo ' npm 版本 : \$(npm -v)' +else + echo ' npm 版本 : n/a' +fi +echo '' +echo ' 当前默认使用的 Node 版本:' +nvm ls 2>/dev/null || echo ' nvm ls 不可用' +" + +echo "" +echo "========================================" +echo " 提示:重新登录终端或执行:" +echo " source ~/.bashrc" +echo " 然后: nvm --version" +echo "========================================" diff --git a/resources/deploy_uv.sh b/resources/deploy_uv.sh new file mode 100644 index 0000000..4dc7daf --- /dev/null +++ b/resources/deploy_uv.sh @@ -0,0 +1,151 @@ +#!/bin/bash +# ============================================================= +# deploy_uv.sh - uv (Python 包管理工具) 一键安装与配置脚本 +# ============================================================= +# 适用系统: Ubuntu 20.04+ +# 功能: 安装 uv、配置国内镜像源加速 +# 说明: 先将安装脚本下载到本地再执行,避免管道问题; +# 如果官方源慢,自动切 GitHub Release 镜像。 +# ============================================================= + +set -e + +echo "========================================" +echo " 开始部署 uv - Python 包管理工具" +echo "========================================" + +# ---------- 1. 安装 uv ---------- +install_uv() { + echo "[1/4] 正在安装 uv..." + + # 先将安装脚本下载到 /tmp(而非管道直接执行) + echo " 正在下载安装脚本..." + + # 尝试从官方源下载(带超时) + if timeout 30 curl -fsSL https://astral.sh/uv/install.sh -o /tmp/uv-install.sh 2>/dev/null; then + echo " 下载源: https://astral.sh/uv/install.sh" + else + # 官方源超时,换 GitHub Release(国内 CDN 更快) + echo " 官方源下载超时,尝试 GitHub Release..." + UV_VERSION=$(curl -fsSL https://api.github.com/repos/astral-sh/uv/releases/latest 2>/dev/null | grep '"tag_name"' | cut -d'"' -f4) + if [ -z "$UV_VERSION" ]; then + UV_VERSION="0.6.14" # fallback 固定版本 + fi + echo " 正在下载 uv ${UV_VERSION} 预编译包..." + curl -fsSL "https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-${UV_VERSION}-x86_64-unknown-linux-gnu.tar.gz" -o /tmp/uv.tar.gz + # 解压并安装 + mkdir -p /tmp/uv-extract + tar xzf /tmp/uv.tar.gz -C /tmp/uv-extract + cp /tmp/uv-extract/uv-${UV_VERSION}-x86_64-unknown-linux-gnu/uv /tmp/uv-install-dir/ 2>/dev/null || true + # 直接复制到 local/bin + mkdir -p "$HOME/.local/bin" + cp /tmp/uv-extract/uv-${UV_VERSION}-x86_64-unknown-linux-gnu/uv "$HOME/.local/bin/uv" + chmod +x "$HOME/.local/bin/uv" + ln -sf "$HOME/.local/bin/uv" "$HOME/.local/bin/uvx" 2>/dev/null || true + rm -rf /tmp/uv.tar.gz /tmp/uv-extract + echo "✅ uv 安装成功(来自 GitHub Release)!" + return 0 + fi + + # 执行安装脚本 + echo " 正在执行安装..." + bash /tmp/uv-install.sh + rm -f /tmp/uv-install.sh + + if [ $? -ne 0 ]; then + echo "❌ uv 安装失败,请检查网络连接后重试。" + exit 1 + fi + + echo "✅ uv 安装成功!" +} + +# ---------- 2. 配置环境变量 ---------- +setup_env() { + echo "[2/4] 正在配置环境变量..." + + # 检测 shell 并添加 uv 到 PATH + SHELL_CONFIG="" + + if [ -n "$BASH_VERSION" ]; then + SHELL_CONFIG="$HOME/.bashrc" + elif [ -n "$ZSH_VERSION" ]; then + SHELL_CONFIG="$HOME/.zshrc" + else + SHELL_CONFIG="$HOME/.profile" + fi + + # 如果 PATH 中还没有 uv 路径,则添加 + if ! echo "$PATH" | grep -q "$HOME/.local/bin"; then + echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$SHELL_CONFIG" + echo "已添加到 $SHELL_CONFIG" + else + echo "$HOME/.local/bin 已在 PATH 中" + fi + + # 临时生效 + export PATH="$HOME/.local/bin:$PATH" + + echo "✅ 环境变量配置完成!" +} + +# ---------- 3. 创建 uv 配置文件(换源) ---------- +config_mirror() { + echo "[3/4] 正在配置国内镜像源(加速下载)..." + + CONFIG_DIR="$HOME/.config/uv" + CONFIG_FILE="$CONFIG_DIR/uv.toml" + + mkdir -p "$CONFIG_DIR" + + cat > "$CONFIG_FILE" << 'EOF' +# uv 全局配置 - 使用国内镜像源加速 + +# Python 版本下载镜像源(提高 `uv python install` 速度) +python-install-mirror = "https://registry.npmmirror.com/-/binary/python-build-standalone/" + +# pip 包下载镜像源(提高 `uv pip install` 速度) +[[index]] +url = "https://mirrors.aliyun.com/pypi/simple/" +default = true +EOF + + echo "配置文件已创建: $CONFIG_FILE" + echo "✅ 镜像源配置完成!" +} + +# ---------- 4. 验证安装 ---------- +verify_install() { + echo "[4/4] 正在验证安装..." + + if command -v uv &> /dev/null; then + UV_VER=$(uv --version) + echo "✅ uv 版本: $UV_VER" + else + echo "❌ uv 命令未找到,请重启终端或执行: source ~/.bashrc" + exit 1 + fi + + if command -v uvx &> /dev/null; then + echo "✅ uvx 已安装(用于运行工具包)" + fi + + echo "" + echo "========================================" + echo " 🎉 uv 部署完成!" + echo "========================================" + echo "" + echo "快速上手:" + echo " uv python install 3.12 # 安装 Python 3.12" + echo " uv venv # 创建虚拟环境" + echo " uv pip install requests # 安装包" + echo " uv run main.py # 运行脚本" + echo "" + echo "重启终端或执行: source $SHELL_CONFIG" +} + +# ---------- 主流程 ---------- +install_uv +setup_env +config_mirror +verify_install diff --git a/scripts/add_script_runner.py b/scripts/add_script_runner.py new file mode 100644 index 0000000..0b0599c --- /dev/null +++ b/scripts/add_script_runner.py @@ -0,0 +1,281 @@ +import os + +gui_path = 'src/virtualbox_client/gui.py' +with open(gui_path, 'r', encoding='utf-8') as f: + content = f.read() + +# ===== 1. 添加 ScriptWorker 类(放在 DownloadWorker 之后) ===== +old = """class WorkspaceWorker(QObject):""" + +new = """class ScriptWorker(QObject): + """上传本地脚本到虚拟机、执行并下载日志""" + log_signal = Signal(str, str) + finished_signal = Signal(bool) + error_signal = Signal(str) + + def __init__(self, config: dict, local_path: str): + super().__init__() + self.config = config + self.local_path = local_path + self.cancel_event = threading.Event() + + def cancel(self): + self.cancel_event.set() + + def run(self): + ssh_only = self.config.get("ssh_only", False) + _com_initialized = False + if not ssh_only: + try: + import ctypes + ctypes.windll.ole32.CoInitializeEx(None, 0) + _com_initialized = True + except Exception: + pass + + try: + if ssh_only: + from virtualbox_client.utils.ssh_manager import SSHManager + vm = SSHManager( + self.config["ssh_host"], self.config["ssh_port"], + self.config["ssh_user"], self.config["ssh_pass"], + ) + else: + from virtualbox_client.utils.vbox_ssh_manager import VBoxSSHManager + vm = VBoxSSHManager( + self.config["vm_name"], self.config["ssh_host"], + self.config["ssh_port"], self.config["ssh_user"], + self.config["ssh_pass"], + ) + vm.set_cancel_event(self.cancel_event) + + if self.cancel_event.is_set(): + return + + import builtins + original_print = print + def custom_print(*args, **kwargs): + msg = " ".join(str(a) for a in args) + self.log_signal.emit(msg, "info") + original_print(*args, **kwargs) + builtins.print = custom_print + + try: + script_name = os.path.basename(self.local_path) + remote_path = f"/tmp/{script_name}" + log_path = f"/tmp/{script_name}.log" + local_log_path = f"{script_name}.log" + + # 1. 上传脚本 + self.log_signal.emit(f"📤 [1/3] 上传脚本: {self.local_path} -> {remote_path}", "info") + if not vm.upload_file(self.local_path, remote_path): + self.log_signal.emit("❌ 上传失败", "error") + return + + # 2. 执行脚本 + self.log_signal.emit(f"⚡ [2/3] 执行脚本: {remote_path}", "info") + cmd = ( + f"chmod +x {remote_path} && " + f"echo '{self.config['ssh_pass']}' | sudo -S {remote_path} 2>&1 | " + f"tee {log_path}" + ) + out, err, code = vm.exec_command(cmd, get_pty=False) + self.log_signal.emit(f"✔ 脚本执行完成,退出码: {code}", "info") + + # 3. 下载日志 + self.log_signal.emit(f"📥 [3/3] 下载日志: {log_path} -> {local_log_path}", "info") + if vm.download_file(log_path, local_log_path): + self.log_signal.emit(f"✔ 日志已保存到本地: {local_log_path}", "success") + else: + self.log_signal.emit("⚠ tee 日志下载失败,尝试保存终端输出", "warning") + if out: + with open(local_log_path, "w") as f: + f.write(out) + self.log_signal.emit(f"✔ 终端输出已保存到: {local_log_path}", "success") + + # 4. 清理远程临时文件 + vm.exec_command(f'rm -f "{remote_path}" "{log_path}"') + self.log_signal.emit("🧹 远程临时文件已清理", "info") + + self.log_signal.emit("🎉 脚本执行全部完成!", "success") + self.finished_signal.emit(True) + finally: + builtins.print = original_print + + except ImportError as e: + self.log_signal.emit(f"❌ 缺少依赖: {e}", "error") + self.error_signal.emit(str(e)) + except Exception as e: + self.log_signal.emit(f"❌ 脚本执行错误: {str(e)}", "error") + self.error_signal.emit(str(e)) + finally: + if _com_initialized: + try: + import ctypes + ctypes.windll.ole32.CoUninitialize() + except Exception: + pass + + +class WorkspaceWorker(QObject):""" + +content = content.replace(old, new) + +# ===== 2. 在 TaskPanel.setup_ui 的文件传输 tab 后面添加脚本执行 tab ===== +old_file_tab_end = """ file_layout.addStretch() + self.tab_widget.addTab(file_tab, "📁 文件传输")""" + +new_file_tab_end = """ file_layout.addStretch() + self.tab_widget.addTab(file_tab, "📁 文件传输") + + # ====== 脚本执行 Tab ====== + script_tab = QWidget() + script_layout = QVBoxLayout(script_tab) + script_layout.setContentsMargins(8, 8, 8, 8) + + script_group = QGroupBox("⚡ 上传并执行脚本") + script_group_layout = QVBoxLayout(script_group) + + script_path_layout = QHBoxLayout() + self.script_path_edit = QLineEdit(placeholderText="选择本地 .sh 脚本文件...") + script_path_layout.addWidget(self.script_path_edit) + self.script_browse_btn = QPushButton("📂 浏览") + self.script_browse_btn.setFixedWidth(80) + self.script_browse_btn.clicked.connect(self._browse_script) + script_path_layout.addWidget(self.script_browse_btn) + script_group_layout.addWidget(QLabel("本地脚本路径:")) + script_group_layout.addLayout(script_path_layout) + + self.run_script_btn = QPushButton("⚡ 上传并执行脚本") + self.run_script_btn.setStyleSheet(\""" + QPushButton { background-color: #8e44ad; color: white; padding: 10px 30px; + font-size: 14px; font-weight: bold; border: none; border-radius: 6px; } + QPushButton:hover { background-color: #9b59b6; } + QPushButton:disabled { background-color: #555; color: #999; } + \""") + self.run_script_btn.clicked.connect(self._run_script) + script_group_layout.addWidget(self.run_script_btn) + + script_layout.addWidget(script_group) + + # 执行说明 + desc_group = QGroupBox("📖 说明") + desc_layout = QVBoxLayout(desc_group) + desc_label = QLabel( + "1. 选择一个本地 .sh 脚本文件\\n" + "2. 点击按钮自动完成:\\n" + " 📤 上传脚本到虚拟机 /tmp/ 目录\\n" + " ⚡ 以 sudo 权限执行脚本\\n" + " 📥 下载执行日志到本地\\n" + " 🧹 清理远程临时文件" + ) + desc_label.setStyleSheet("color: #aaa; font-size: 12px; line-height: 1.6;") + desc_layout.addWidget(desc_label) + script_layout.addWidget(desc_group) + + script_layout.addStretch() + self.tab_widget.addTab(script_tab, "⚡ 脚本执行")""" + +content = content.replace(old_file_tab_end, new_file_tab_end) + +# ===== 3. 在 TaskPanel 的 _set_ui_running 中禁用脚本按钮 ===== +old_running = """ def _set_ui_running(self, running: bool): + self.run_btn.setEnabled(not running) + self.cancel_btn.setEnabled(running) + self.task_input.setEnabled(not running) + if self._config_panel: + self._config_panel.set_enabled(not running)""" + +new_running = """ def _set_ui_running(self, running: bool): + self.run_btn.setEnabled(not running) + self.cancel_btn.setEnabled(running) + self.task_input.setEnabled(not running) + self.run_script_btn.setEnabled(not running) + self.script_browse_btn.setEnabled(not running) + self.script_path_edit.setEnabled(not running) + if self._config_panel: + self._config_panel.set_enabled(not running)""" + +content = content.replace(old_running, new_running) + +# ===== 4. 在 TaskPanel 中添加 _browse_script 和 _run_script 方法 ===== +# 放在 _on_download_error 之后、class MainWindow 之前 +old_mainwindow = """class MainWindow(QMainWindow):""" + +new_methods = """ _script_thread = None + _script_worker = None + + def _cleanup_script(self): + if self._script_thread: + try: + self._script_thread.finished.disconnect() + except (TypeError, RuntimeError): + pass + if self._script_thread.isRunning(): + self._script_thread.quit() + if not self._script_thread.wait(5000): + self._script_thread.terminate() + self._script_thread.wait(3000) + self._script_thread = None + if self._script_worker: + try: + self._script_worker.log_signal.disconnect() + self._script_worker.finished_signal.disconnect() + self._script_worker.error_signal.disconnect() + except (TypeError, RuntimeError): + pass + self._script_worker = None + + def _browse_script(self): + file_path, _ = QFileDialog.getOpenFileName( + self, "选择要执行的脚本", "", "Shell 脚本 (*.sh);;所有文件 (*)" + ) + if file_path: + self.script_path_edit.setText(file_path) + + def _run_script(self): + if not self._config_panel or not self._config_panel.validate(): + return + local_path = self.script_path_edit.text().strip() + if not local_path: + QMessageBox.warning(self, "提示", "请选择要执行的脚本文件") + return + if not os.path.isfile(local_path): + QMessageBox.warning(self, "提示", f"本地脚本不存在: {local_path}") + return + + self._cleanup_script() + self._set_ui_running(True) + self.tab_widget.setCurrentIndex(0) # 切换到任务日志 tab + self.log_panel.clear_log() + + config = self._config_panel.get_config() + self._script_worker = ScriptWorker(config, local_path) + self._script_thread = QThread(self) + self._script_worker.moveToThread(self._script_thread) + + self._script_worker.log_signal.connect(self.log_panel.append_log) + self._script_worker.finished_signal.connect(self._on_script_finished) + self._script_worker.error_signal.connect(self._on_script_error) + self._script_thread.started.connect(self._script_worker.run) + self._script_thread.finished.connect(self._cleanup_script) + + self._script_thread.start() + + def _on_script_finished(self, success: bool): + self._set_ui_running(False) + if success: + self.log_panel.append_log("✅ 脚本执行已完成", "success") + + def _on_script_error(self, error_msg: str): + self._set_ui_running(False) + self.log_panel.append_log(f"❌ 脚本执行失败: {error_msg}", "error") + + +""" + old_mainwindow + +content = content.replace(old_mainwindow, new_methods) + +with open(gui_path, 'w', encoding='utf-8') as f: + f.write(content) +print('✅ gui.py 已集成脚本执行功能') diff --git a/scripts/add_script_tab.py b/scripts/add_script_tab.py new file mode 100644 index 0000000..953808e --- /dev/null +++ b/scripts/add_script_tab.py @@ -0,0 +1,275 @@ +# This script adds ScriptWorker and a script execution tab to gui.py +import os + +gui_path = "src/virtualbox_client/gui.py" +with open(gui_path, "r", encoding="utf-8") as f: + content = f.read() + +# 1. Add ScriptWorker before WorkspaceWorker +old_worker = "class WorkspaceWorker(QObject):" + +new_worker = """class ScriptWorker(QObject): + log_signal = Signal(str, str) + finished_signal = Signal(bool) + error_signal = Signal(str) + + def __init__(self, config: dict, local_path: str): + super().__init__() + self.config = config + self.local_path = local_path + self.cancel_event = threading.Event() + + def cancel(self): + self.cancel_event.set() + + def run(self): + ssh_only = self.config.get("ssh_only", False) + _com_initialized = False + if not ssh_only: + try: + import ctypes + ctypes.windll.ole32.CoInitializeEx(None, 0) + _com_initialized = True + except Exception: + pass + + try: + if ssh_only: + from virtualbox_client.utils.ssh_manager import SSHManager + vm = SSHManager( + self.config["ssh_host"], self.config["ssh_port"], + self.config["ssh_user"], self.config["ssh_pass"], + ) + else: + from virtualbox_client.utils.vbox_ssh_manager import VBoxSSHManager + vm = VBoxSSHManager( + self.config["vm_name"], self.config["ssh_host"], + self.config["ssh_port"], self.config["ssh_user"], + self.config["ssh_pass"], + ) + vm.set_cancel_event(self.cancel_event) + + if self.cancel_event.is_set(): + return + + import builtins + original_print = print + def custom_print(*args, **kwargs): + msg = " ".join(str(a) for a in args) + self.log_signal.emit(msg, "info") + original_print(*args, **kwargs) + builtins.print = custom_print + + try: + import os as _os + script_name = _os.path.basename(self.local_path) + remote_path = "/tmp/" + script_name + log_path = "/tmp/" + script_name + ".log" + local_log_path = script_name + ".log" + + self.log_signal.emit("[1/3] Upload: " + self.local_path + " -> " + remote_path, "info") + if not vm.upload_file(self.local_path, remote_path): + self.log_signal.emit("Upload failed", "error") + return + + self.log_signal.emit("[2/3] Execute: " + remote_path, "info") + cmd = ( + "chmod +x " + remote_path + " && " + "echo '" + self.config["ssh_pass"] + "' | sudo -S " + remote_path + " 2>&1 | " + "tee " + log_path + ) + out, err, code = vm.exec_command(cmd, get_pty=False) + self.log_signal.emit("Exit code: " + str(code), "info") + + self.log_signal.emit("[3/3] Download log: " + log_path + " -> " + local_log_path, "info") + if vm.download_file(log_path, local_log_path): + self.log_signal.emit("Log saved: " + local_log_path, "success") + else: + self.log_signal.emit("Tee log download failed, saving terminal output", "warning") + if out: + with open(local_log_path, "w") as f: + f.write(out) + self.log_signal.emit("Output saved: " + local_log_path, "success") + + vm.exec_command("rm -f \\"" + remote_path + "\\" \\"" + log_path + "\\"") + self.log_signal.emit("Remote temp files cleaned", "info") + self.log_signal.emit("Script execution complete!", "success") + self.finished_signal.emit(True) + finally: + builtins.print = original_print + + except ImportError as e: + self.log_signal.emit("Missing dependency: " + str(e), "error") + self.error_signal.emit(str(e)) + except Exception as e: + self.log_signal.emit("Script error: " + str(e), "error") + self.error_signal.emit(str(e)) + finally: + if _com_initialized: + try: + import ctypes + ctypes.windll.ole32.CoUninitialize() + except Exception: + pass + + +""" + old_worker + +content = content.replace(old_worker, new_worker) + +# 2. Add script tab after file transfer tab +old_tab_end = ' file_layout.addStretch()\n self.tab_widget.addTab(file_tab, "\U0001f4c1 \u6587\u4ef6\u4f20\u8f93")' + +new_tab_end = ''' file_layout.addStretch() + self.tab_widget.addTab(file_tab, "\U0001f4c1 \u6587\u4ef6\u4f20\u8f93") + + # ====== Script Execution Tab ====== + script_tab = QWidget() + script_layout = QVBoxLayout(script_tab) + script_layout.setContentsMargins(8, 8, 8, 8) + + script_group = QGroupBox("Upload and Execute Script") + script_group_layout = QVBoxLayout(script_group) + + script_path_layout = QHBoxLayout() + self.script_path_edit = QLineEdit(placeholderText="Select local .sh script file...") + script_path_layout.addWidget(self.script_path_edit) + self.script_browse_btn = QPushButton("Browse") + self.script_browse_btn.setFixedWidth(80) + self.script_browse_btn.clicked.connect(self._browse_script) + script_path_layout.addWidget(self.script_browse_btn) + script_group_layout.addWidget(QLabel("Local Script:")) + script_group_layout.addLayout(script_path_layout) + + self.run_script_btn = QPushButton("Upload and Execute Script") + self.run_script_btn.setStyleSheet(""" + QPushButton { background-color: #8e44ad; color: white; padding: 10px 30px; + font-size: 14px; font-weight: bold; border: none; border-radius: 6px; } + QPushButton:hover { background-color: #9b59b6; } + QPushButton:disabled { background-color: #555; color: #999; } + """) + self.run_script_btn.clicked.connect(self._run_script) + script_group_layout.addWidget(self.run_script_btn) + + script_layout.addWidget(script_group) + + desc_group = QGroupBox("How it works") + desc_layout = QVBoxLayout(desc_group) + desc_label = QLabel( + "1. Select a local .sh script file\\n" + "2. Click button to auto:\\n" + " Upload script to /tmp/\\n" + " Execute with sudo\\n" + " Download execution log\\n" + " Clean remote temp files" + ) + desc_label.setStyleSheet("color: #aaa; font-size: 12px; line-height: 1.6;") + desc_layout.addWidget(desc_label) + script_layout.addWidget(desc_group) + + script_layout.addStretch() + self.tab_widget.addTab(script_tab, "Script")''' + +content = content.replace(old_tab_end, new_tab_end) + +# 3. Update _set_ui_running +old_running = """ def _set_ui_running(self, running: bool): + self.run_btn.setEnabled(not running) + self.cancel_btn.setEnabled(running) + self.task_input.setEnabled(not running) + if self._config_panel: + self._config_panel.set_enabled(not running)""" + +new_running = """ def _set_ui_running(self, running: bool): + self.run_btn.setEnabled(not running) + self.cancel_btn.setEnabled(running) + self.task_input.setEnabled(not running) + self.run_script_btn.setEnabled(not running) + self.script_browse_btn.setEnabled(not running) + self.script_path_edit.setEnabled(not running) + if self._config_panel: + self._config_panel.set_enabled(not running)""" + +content = content.replace(old_running, new_running) + +# 4. Add methods before MainWindow +old_mw = "class MainWindow(QMainWindow):" + +new_mw = """ _script_thread = None + _script_worker = None + + def _cleanup_script(self): + if self._script_thread: + try: + self._script_thread.finished.disconnect() + except (TypeError, RuntimeError): + pass + if self._script_thread.isRunning(): + self._script_thread.quit() + if not self._script_thread.wait(5000): + self._script_thread.terminate() + self._script_thread.wait(3000) + self._script_thread = None + if self._script_worker: + try: + self._script_worker.log_signal.disconnect() + self._script_worker.finished_signal.disconnect() + self._script_worker.error_signal.disconnect() + except (TypeError, RuntimeError): + pass + self._script_worker = None + + def _browse_script(self): + file_path, _ = QFileDialog.getOpenFileName( + self, "Select script", "", "Shell scripts (*.sh);;All files (*)" + ) + if file_path: + self.script_path_edit.setText(file_path) + + def _run_script(self): + if not self._config_panel or not self._config_panel.validate(): + return + local_path = self.script_path_edit.text().strip() + if not local_path: + QMessageBox.warning(self, "Hint", "Please select a script file") + return + if not os.path.isfile(local_path): + QMessageBox.warning(self, "Hint", "File not found: " + local_path) + return + + self._cleanup_script() + self._set_ui_running(True) + self.tab_widget.setCurrentIndex(0) + self.log_panel.clear_log() + + config = self._config_panel.get_config() + self._script_worker = ScriptWorker(config, local_path) + self._script_thread = QThread(self) + self._script_worker.moveToThread(self._script_thread) + + self._script_worker.log_signal.connect(self.log_panel.append_log) + self._script_worker.finished_signal.connect(self._on_script_finished) + self._script_worker.error_signal.connect(self._on_script_error) + self._script_thread.started.connect(self._script_worker.run) + self._script_thread.finished.connect(self._cleanup_script) + + self._script_thread.start() + + def _on_script_finished(self, success: bool): + self._set_ui_running(False) + if success: + self.log_panel.append_log("Script execution completed", "success") + + def _on_script_error(self, error_msg: str): + self._set_ui_running(False) + self.log_panel.append_log("Script error: " + error_msg, "error") + + +""" + old_mw + +content = content.replace(old_mw, new_mw) + +with open(gui_path, "w", encoding="utf-8") as f: + f.write(content) + +print("Done") diff --git a/scripts/check_syntax.py b/scripts/check_syntax.py new file mode 100644 index 0000000..9be9a33 --- /dev/null +++ b/scripts/check_syntax.py @@ -0,0 +1,18 @@ +import ast +import sys + +files = [ + "src/virtualbox_client/gui.py", + "src/virtualbox_client/utils/vbox_ssh_manager.py", + "src/virtualbox_client/utils/vm_agent.py", +] + +for f in files: + try: + tree = ast.parse(open(f, encoding="utf-8").read()) + print(f"OK: {f.split(chr(47))[-1]} ({len(tree.body)} nodes)") + except SyntaxError as e: + print(f"FAIL: {f.split(chr(47))[-1]}: {e}") + sys.exit(1) + +print("All files pass syntax check") diff --git a/scripts/clean_pycache.py b/scripts/clean_pycache.py new file mode 100644 index 0000000..d402b80 --- /dev/null +++ b/scripts/clean_pycache.py @@ -0,0 +1,13 @@ +import os +import shutil + +# 需要遍历的目录 +root_dir = "./" +# 遍历目录 +for dirpath, dirnames, filenames in os.walk(root_dir): + if "__pycache__" in dirnames: + # 获取 __pycache__ 目录的全路径 + pycache_dir = os.path.join(dirpath, "__pycache__") + # 删除目录 + shutil.rmtree(pycache_dir) + print(f"Removed: {pycache_dir}") diff --git a/scripts/code_content.py b/scripts/code_content.py new file mode 100644 index 0000000..a241f36 --- /dev/null +++ b/scripts/code_content.py @@ -0,0 +1,45 @@ +import os +import sys + + +def generate_markdown_from_py_files(directory, output_file): + with open(output_file, "w", encoding="utf-8") as md_file: + for root, dirs, files in os.walk(directory): + # 排除 venv 目录 + dirs[:] = [d for d in dirs if d != ".venv"] + dirs[:] = [d for d in dirs if d != ".vscode"] + dirs[:] = [d for d in dirs if d != "node_modules"] + dirs[:] = [d for d in dirs if d != "build"] + dirs[:] = [d for d in dirs if d != "dist"] + dirs[:] = [d for d in dirs if d != ".sdk"] + for file in files: + if ( + file.endswith(".py") + or file.endswith(".rs") + or file.endswith(".toml") + or file.endswith(".md") + or file.endswith(".css") + or file.endswith(".vue") + or file.endswith(".ts") + or file.endswith(".json") + or file.endswith(".html") + or file.endswith(".sh") + or file == "Dockerfile" + ): + file_path = os.path.join(root, file) + if file == "output.md" or file == "package-lock.json": + continue + md_file.write(f"`{file_path}`\n") + md_file.write("```\n") + with open(file_path, "r", encoding="utf-8") as py_file: + md_file.write(py_file.read()) + md_file.write("\n```\n\n") + + +if __name__ == "__main__": + # 指定目录和输出文件名 + target_directory = sys.argv[1] # 替换为你的目标目录 + output_markdown_file = "output.md" # 输出的 Markdown 文件名 + + generate_markdown_from_py_files(target_directory, output_markdown_file) + print(f"Markdown 文件已生成:{output_markdown_file}") diff --git a/scripts/fix_paths.py b/scripts/fix_paths.py new file mode 100644 index 0000000..598a829 --- /dev/null +++ b/scripts/fix_paths.py @@ -0,0 +1,30 @@ +content = open("src/virtualbox_client/gui.py", encoding="utf-8").read() + +# 1. 修复 except 语法 +content = content.replace( + "except TypeError, RuntimeError:", "except (TypeError, RuntimeError):" +) + +# 2. 修复 _rename_item 中的路径分隔符 +old = """ dst_dir = os.path.dirname(src_path) + dst_path = os.path.join(dst_dir, new_name.strip())""" +new = """ dst_dir = src_path.rsplit('/', 1)[0] if '/' in src_path else src_path.rsplit('\\\\', 1)[0] + dst_path = dst_dir.rstrip('/') + '/' + new_name.strip()""" +content = content.replace(old, new) + +# 3. 修复 _copy_item 中的路径分隔符 +old2 = """ dst_dir = os.path.dirname(src_path) + dst_path = os.path.join(dst_dir, new_name)""" +new2 = """ dst_dir = src_path.rsplit('/', 1)[0] if '/' in src_path else src_path.rsplit('\\\\', 1)[0] + dst_path = dst_dir.rstrip('/') + '/' + new_name""" +content = content.replace(old2, new2) + +# 4. 修复 _upload_file 中的 remote_dir 路径 +old3 = """ remote_dir = os.path.dirname(remote_path) + vm.exec_command(f'mkdir -p "{remote_dir}"')""" +new3 = """ remote_dir = remote_path.rsplit('/', 1)[0] if '/' in remote_path else remote_path.rsplit('\\\\', 1)[0] + vm.exec_command(f'mkdir -p "{remote_dir}"')""" +content = content.replace(old3, new3) + +open("src/virtualbox_client/gui.py", "w", encoding="utf-8").write(content) +print("✅ 所有修复完成") diff --git a/scripts/fix_upload_folder.py b/scripts/fix_upload_folder.py new file mode 100644 index 0000000..f5fecf7 --- /dev/null +++ b/scripts/fix_upload_folder.py @@ -0,0 +1,226 @@ +import os + +# 1. 给 SSHManager 添加 upload_directory 方法 +ssh_path = "src/virtualbox_client/utils/ssh_manager.py" +with open(ssh_path, "r", encoding="utf-8") as f: + content = f.read() + +old_method = """ def upload_file(self, local_path, remote_path): + \"\"\"上传本地文件到远程服务器,返回是否成功\"\"\" + client = self.get_ssh_client() + if not client: + return False + try: + sftp = client.open_sftp() + sftp.put(local_path, remote_path) + sftp.close() + print(f\"✔ 上传成功: {local_path} -> {remote_path}\") + return True + except Exception as e: + if self._cancelled.is_set(): + return False + print(f\"✘ 上传失败: {e}\") + return False +""" + +new_method = """ def upload_file(self, local_path, remote_path): + \"\"\"上传本地文件到远程服务器,返回是否成功\"\"\" + client = self.get_ssh_client() + if not client: + return False + try: + sftp = client.open_sftp() + sftp.put(local_path, remote_path) + sftp.close() + print(f\"✔ 上传成功: {local_path} -> {remote_path}\") + return True + except Exception as e: + if self._cancelled.is_set(): + return False + print(f\"✘ 上传失败: {e}\") + return False + + def upload_directory(self, local_dir, remote_dir): + \"\"\"递归上传整个文件夹到远程服务器,返回是否成功\"\"\" + import os as _os + client = self.get_ssh_client() + if not client: + return False + try: + sftp = client.open_sftp() + local_dir = _os.path.normpath(local_dir) + remote_dir = remote_dir.rstrip('/') + # 创建远程根目录 + self.exec_command(f'mkdir -p \"{remote_dir}\"') + for root, dirs, files in _os.walk(local_dir): + # 计算相对路径 + rel_path = _os.path.relpath(root, local_dir) + if rel_path == '.': + rel_path = '' + # 创建远程子目录 + target_dir = remote_dir + ('/' + rel_path.replace('\\\\', '/') if rel_path else '') + self.exec_command(f'mkdir -p \"{target_dir}\"') + for fname in files: + if self._cancelled.is_set(): + sftp.close() + return False + local_file = _os.path.join(root, fname) + remote_file = target_dir + '/' + fname + sftp.put(local_file, remote_file) + print(f\" ✔ {local_file} -> {remote_file}\") + sftp.close() + print(f\"✔ 文件夹上传成功: {local_dir} -> {remote_dir}\") + return True + except Exception as e: + if self._cancelled.is_set(): + return False + print(f\"✘ 文件夹上传失败: {e}\") + return False +""" + +content = content.replace(old_method, new_method) +with open(ssh_path, "w", encoding="utf-8") as f: + f.write(content) +print("✅ SSHManager 已添加 upload_directory") + +# 2. 给 VBoxSSHManager 添加 upload_directory 方法 +vbox_path = "src/virtualbox_client/utils/vbox_ssh_manager.py" +with open(vbox_path, "r", encoding="utf-8") as f: + content = f.read() + +content = content.replace(old_method, new_method) +with open(vbox_path, "w", encoding="utf-8") as f: + f.write(content) +print("✅ VBoxSSHManager 已添加 upload_directory") + +# 3. 修改 gui.py - 添加上传文件夹功能 +gui_path = "src/virtualbox_client/gui.py" +with open(gui_path, "r", encoding="utf-8") as f: + content = f.read() + +# 在 WorkspaceWorker 中添加 OPERATION_UPLOAD_DIR +old_op = ' OPERATION_UPLOAD = "upload"\n' +new_op = ' OPERATION_UPLOAD = "upload"\n OPERATION_UPLOAD_DIR = "upload_dir"\n' +content = content.replace(old_op, new_op) + +# 在 run 方法中添加分支 +old_run = """ elif self.operation == WorkspaceWorker.OPERATION_UPLOAD: + result = self._upload_file(vm)""" +new_run = """ elif self.operation == WorkspaceWorker.OPERATION_UPLOAD: + result = self._upload_file(vm) + elif self.operation == WorkspaceWorker.OPERATION_UPLOAD_DIR: + result = self._upload_directory(vm)""" +content = content.replace(old_run, new_run) + +# 在 _upload_file 之后添加 _upload_directory 方法 +old_upload = """ def _upload_file(self, vm) -> bool: + local_path = self.kwargs.get("local_path", "") + remote_path = self.kwargs.get("remote_path", "") + if not local_path or not remote_path: + return False + remote_dir = remote_path.rsplit('/', 1)[0] if '/' in remote_path else remote_path.rsplit('\\\\', 1)[0] + vm.exec_command(f'mkdir -p "{remote_dir}"') + return vm.upload_file(local_path, remote_path)""" + +new_upload = """ def _upload_file(self, vm) -> bool: + local_path = self.kwargs.get("local_path", "") + remote_path = self.kwargs.get("remote_path", "") + if not local_path or not remote_path: + return False + remote_dir = remote_path.rsplit('/', 1)[0] if '/' in remote_path else remote_path.rsplit('\\\\', 1)[0] + vm.exec_command(f'mkdir -p "{remote_dir}"') + return vm.upload_file(local_path, remote_path) + + def _upload_directory(self, vm) -> bool: + local_dir = self.kwargs.get("local_path", "") + remote_dir = self.kwargs.get("remote_path", "") + if not local_dir or not remote_dir: + return False + return vm.upload_directory(local_dir, remote_dir)""" + +content = content.replace(old_upload, new_upload) + +# 修改 _on_worker_finished 添加 UPLOAD_DIR 处理 +old_finish = """ elif ( + self._worker and self._worker.operation == WorkspaceWorker.OPERATION_UPLOAD + ): + if result: + self.log_panel.append_log("✅ 上传成功", "success") + else: + self.log_panel.append_log("❌ 上传失败", "error") + QTimer.singleShot(500, self._soft_refresh)""" + +new_finish = """ elif ( + self._worker + and self._worker.operation + in (WorkspaceWorker.OPERATION_UPLOAD, WorkspaceWorker.OPERATION_UPLOAD_DIR) + ): + if result: + self.log_panel.append_log("✅ 上传成功", "success") + else: + self.log_panel.append_log("❌ 上传失败", "error") + QTimer.singleShot(500, self._soft_refresh)""" + +content = content.replace(old_finish, new_finish) + +# 在 WorkspacePanel.setup_ui 的工具栏中添加"上传文件夹"按钮 +old_upload_btn = """ self.upload_btn = QPushButton("📤 上传文件")""" +new_upload_btn = """ self.upload_btn = QPushButton("📤 上传文件")""" +# 保持原样,在后面添加新按钮 +# 在 upload_btn 后面添加 upload_dir_btn +old_toolbar = """ toolbar.addWidget(self.upload_btn) + + self.mkdir_btn = QPushButton("📂 新建文件夹")""" +new_toolbar = """ toolbar.addWidget(self.upload_btn) + + self.upload_dir_btn = QPushButton("📁 上传文件夹") + self.upload_dir_btn.setStyleSheet(\"\"\" + QPushButton { background-color: #16a085; color: white; padding: 6px 14px; + font-size: 12px; font-weight: bold; border: none; border-radius: 4px; } + QPushButton:hover { background-color: #1abc9c; } + QPushButton:disabled { background-color: #555; color: #999; } + \"\"\") + self.upload_dir_btn.clicked.connect(self._upload_folder_to_workspace) + toolbar.addWidget(self.upload_dir_btn) + + self.mkdir_btn = QPushButton("📂 新建文件夹")""" +content = content.replace(old_toolbar, new_toolbar) + +# 更新 _set_busy 禁用 upload_dir_btn +old_set_busy = """ self.upload_btn.setEnabled(not busy) + self.mkdir_btn.setEnabled(not busy) + self.home_btn.setEnabled(not busy)""" +new_set_busy = """ self.upload_btn.setEnabled(not busy) + self.upload_dir_btn.setEnabled(not busy) + self.mkdir_btn.setEnabled(not busy) + self.home_btn.setEnabled(not busy)""" +content = content.replace(old_set_busy, new_set_busy) + +# 在 _upload_to_workspace 方法之后添加 _upload_folder_to_workspace +old_upload_workspace = """ def _upload_to_workspace(self):""" + +new_upload_workspace = """ def _upload_folder_to_workspace(self): + if not self._config_panel or not self._validate_ssh_config(): + return + + local_dir = QFileDialog.getExistingDirectory(self, "选择要上传到工作区的文件夹") + if not local_dir: + return + + folder_name = os.path.basename(local_dir.rstrip('/\\\\')) + remote_path = f"~/workspace/{folder_name}" + self._start_worker( + WorkspaceWorker.OPERATION_UPLOAD_DIR, + local_path=local_dir, + remote_path=remote_path, + ) + + def _upload_to_workspace(self):""" + +content = content.replace(old_upload_workspace, new_upload_workspace) + +with open(gui_path, "w", encoding="utf-8") as f: + f.write(content) +print("✅ gui.py 已添加上传文件夹功能") + +print("\\n所有修改完成!") diff --git a/src/virtualbox_client/__init__.py b/src/virtualbox_client/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/virtualbox_client/gui.py b/src/virtualbox_client/gui.py new file mode 100644 index 0000000..48e7fd3 --- /dev/null +++ b/src/virtualbox_client/gui.py @@ -0,0 +1,2568 @@ +""" +VirtualBox Client - PySide6 图形界面 +""" + +import sys +import os +import json +import threading +import time +from datetime import datetime + +try: + from dotenv import load_dotenv + + load_dotenv("./.env.example") +except ImportError: + pass + +from PySide6.QtWidgets import ( + QApplication, + QMainWindow, + QWidget, + QVBoxLayout, + QHBoxLayout, + QGroupBox, + QFormLayout, + QLabel, + QLineEdit, + QSpinBox, + QPushButton, + QPlainTextEdit, + QSplitter, + QStatusBar, + QMessageBox, + QFrame, + QScrollArea, + QCheckBox, + QFileDialog, + QTabWidget, + QTreeWidget, + QTreeWidgetItem, + QHeaderView, + QMenu, + QInputDialog, + QAbstractItemView, + QProgressBar, +) +from PySide6.QtCore import Qt, QObject, QThread, Signal, QTimer, QSize +from PySide6.QtGui import QFont, QTextCursor, QAction, QIcon, QColor, QBrush + + +class AgentWorker(QObject): + """ + 后台线程执行 Agent 任务,通过 Qt Signal 发送日志到主线程。 + 使用 Signal 机制避免了定时器轮询队列带来的延迟和性能开销。 + 支持 VirtualBox 模式和纯 SSH 模式两种运行方式。 + """ + + # 定义信号:日志消息、任务完成(携带 vm_started_by_us)、任务错误、任务取消 + log_signal = Signal(str, str) # (message, level) + finished_signal = Signal(bool) # vm_started_by_us:是否由本任务启动的虚拟机 + error_signal = Signal(str) + cancelled_signal = Signal() + + def __init__(self, config: dict, task: str): + super().__init__() + self.config = config + self.task = task + # 使用共享的 threading.Event,同时控制 AgentWorker 和底层管理器的取消 + self.cancel_event = threading.Event() + # 保存 vm 引用以便在主线程也能取消底层操作 + self._vm = None + + def cancel(self): + """取消任务——设置取消事件,通知所有层级的操作立即停止""" + self.cancel_event.set() + if self._vm is not None: + try: + self._vm.cancel_operations() + except Exception: + pass + + def get_vm(self): + """获取 VM 管理器引用(供主线程关闭 VM 使用)""" + return self._vm + + def is_cancelled(self): + return self.cancel_event.is_set() + + def run(self): + # Windows COM 初始化(仅 VirtualBox 模式需要) + _com_initialized = False + ssh_only = self.config.get("ssh_only", False) + if not ssh_only: + try: + import ctypes + + ctypes.windll.ole32.CoInitializeEx(None, 0) + _com_initialized = True + except Exception: + pass + + try: + from virtualbox_client.utils.vm_agent import VMAgent + + if ssh_only: + from virtualbox_client.utils.ssh_manager import SSHManager + + vm = SSHManager( + self.config["ssh_host"], + self.config["ssh_port"], + self.config["ssh_user"], + self.config["ssh_pass"], + ) + self.log_signal.emit("🔌 使用纯 SSH 模式(不依赖 VirtualBox)", "info") + else: + from virtualbox_client.utils.vbox_ssh_manager import VBoxSSHManager + + vm = VBoxSSHManager( + self.config["vm_name"], + self.config["ssh_host"], + self.config["ssh_port"], + self.config["ssh_user"], + self.config["ssh_pass"], + ) + + vm.set_cancel_event(self.cancel_event) + self._vm = vm + + if self.is_cancelled(): + self.cancelled_signal.emit() + return + + agent = VMAgent( + vm, + api_key=self.config["api_key"], + base_url=self.config.get("base_url", ""), + model=self.config.get("model", "gpt-4o"), + sudo_password=self.config.get("ssh_pass", ""), + ) + + original_print = print + + def custom_print(*args, **kwargs): + msg = " ".join(str(a) for a in args) + self.log_signal.emit(msg, "info") + original_print(*args, **kwargs) + + import builtins + + builtins.print = custom_print + + try: + if self.is_cancelled(): + self.cancelled_signal.emit() + return + + self.log_signal.emit(f"📋 {self.task}", "task") + _, vm_started_by_us = agent.run(self.task) + + if self.is_cancelled(): + self.cancelled_signal.emit() + return + + self.log_signal.emit("✅ 任务完成", "success") + self.finished_signal.emit(vm_started_by_us) + finally: + builtins.print = original_print + + except ImportError as e: + self.log_signal.emit(f"❌ 缺少依赖: {e}", "error") + self.error_signal.emit(str(e)) + except Exception as e: + self.log_signal.emit(f"❌ 错误: {str(e)}", "error") + self.error_signal.emit(str(e)) + finally: + if _com_initialized: + try: + import ctypes + + ctypes.windll.ole32.CoUninitialize() + except Exception: + pass + + +class ShutdownWorker(QObject): + """后台关闭虚拟机的工作线程——避免阻塞 UI 和跨线程 COM 冲突""" + + finished = Signal() + error = Signal(str) + log = Signal(str, str) + + def __init__(self, config: dict): + super().__init__() + self.config = config + + def run(self): + ssh_only = self.config.get("ssh_only", False) + if not ssh_only: + try: + import ctypes + + ctypes.windll.ole32.CoInitializeEx(None, 0) + except Exception: + pass + try: + if ssh_only: + from virtualbox_client.utils.ssh_manager import SSHManager + + self.log.emit("🔌 纯 SSH 模式:正在关闭 SSH 连接...", "info") + vm = SSHManager( + self.config["ssh_host"], + self.config["ssh_port"], + self.config["ssh_user"], + self.config["ssh_pass"], + ) + vm.close_ssh() + self.log.emit("✅ SSH 连接已关闭", "success") + else: + from virtualbox_client.utils.vbox_ssh_manager import VBoxSSHManager + + self.log.emit("🛑 正在关闭虚拟机...", "warning") + vm = VBoxSSHManager( + self.config["vm_name"], + self.config["ssh_host"], + self.config["ssh_port"], + self.config["ssh_user"], + self.config["ssh_pass"], + ) + vm.stop_vm() + self.log.emit("✅ 虚拟机已关闭", "success") + self.finished.emit() + except Exception as e: + self.log.emit(f"❌ 关闭失败: {e}", "error") + self.error.emit(str(e)) + finally: + if not ssh_only: + try: + import ctypes + + ctypes.windll.ole32.CoUninitialize() + except Exception: + pass + + +class ConfigPanel(QWidget): + """配置面板""" + + def __init__(self): + super().__init__() + self.setup_ui() + self.load_from_env() + + def setup_ui(self): + layout = QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + + mode_group = QGroupBox("运行模式") + mode_layout = QVBoxLayout(mode_group) + self.ssh_only_check = QCheckBox("🔌 纯 SSH 模式(不依赖 VirtualBox)") + self.ssh_only_check.setToolTip( + "勾选后只需填写 SSH 连接信息,不需要 VirtualBox 环境和虚拟机名称" + ) + self.ssh_only_check.toggled.connect(self._on_mode_changed) + mode_layout.addWidget(self.ssh_only_check) + layout.addWidget(mode_group) + + vm_group = QGroupBox("虚拟机配置") + vm_form = QFormLayout(vm_group) + self.vm_name_edit = QLineEdit(placeholderText="例如: ubuntu20.04") + vm_form.addRow("虚拟机名称:", self.vm_name_edit) + self.ssh_host_edit = QLineEdit(placeholderText="例如: 127.0.0.1") + vm_form.addRow("SSH 主机:", self.ssh_host_edit) + self.ssh_port_spin = QSpinBox() + self.ssh_port_spin.setRange(1, 65535) + self.ssh_port_spin.setValue(2222) + vm_form.addRow("SSH 端口:", self.ssh_port_spin) + self.ssh_user_edit = QLineEdit(placeholderText="SSH 用户名") + vm_form.addRow("SSH 用户:", self.ssh_user_edit) + self.ssh_pass_edit = QLineEdit( + echoMode=QLineEdit.EchoMode.Password, placeholderText="SSH 密码" + ) + vm_form.addRow("SSH 密码:", self.ssh_pass_edit) + layout.addWidget(vm_group) + + llm_group = QGroupBox("大模型配置") + llm_form = QFormLayout(llm_group) + self.api_key_edit = QLineEdit( + echoMode=QLineEdit.EchoMode.Password, placeholderText="API Key" + ) + llm_form.addRow("API Key:", self.api_key_edit) + self.base_url_edit = QLineEdit(placeholderText="例如: https://api.deepseek.com") + llm_form.addRow("Base URL:", self.base_url_edit) + self.model_edit = QLineEdit(placeholderText="例如: deepseek-v4-flash") + llm_form.addRow("模型:", self.model_edit) + layout.addWidget(llm_group) + + btn_layout = QHBoxLayout() + self.load_env_btn = QPushButton("📂 从 .env 加载") + self.load_env_btn.clicked.connect(self.load_from_env) + btn_layout.addWidget(self.load_env_btn) + self.save_config_btn = QPushButton("💾 保存配置") + self.save_config_btn.clicked.connect(self.save_config) + btn_layout.addWidget(self.save_config_btn) + layout.addLayout(btn_layout) + layout.addStretch() + + def load_from_env(self): + self.vm_name_edit.setText(os.getenv("VM_NAME", "")) + self.ssh_host_edit.setText(os.getenv("VM_SSH_HOST", "127.0.0.1")) + try: + self.ssh_port_spin.setValue(int(os.getenv("VM_SSH_PORT", "2222"))) + except ValueError: + self.ssh_port_spin.setValue(2222) + self.ssh_user_edit.setText(os.getenv("VM_SSH_USER", "")) + self.ssh_pass_edit.setText(os.getenv("VM_SSH_PASS", "")) + self.api_key_edit.setText(os.getenv("LLM_API_KEY", "")) + self.base_url_edit.setText(os.getenv("LLM_BASE_URL", "")) + self.model_edit.setText(os.getenv("LLM_MODEL", "")) + + def save_config(self): + config = self.get_config() + try: + config_path = os.path.join( + os.path.dirname(__file__), "..", "..", "gui_config.json" + ) + with open(config_path, "w", encoding="utf-8") as f: + json.dump(config, f, indent=2, ensure_ascii=False) + QMessageBox.information(self, "保存成功", f"配置已保存到 {config_path}") + except Exception as e: + QMessageBox.warning(self, "保存失败", f"保存配置失败: {e}") + + def _on_mode_changed(self): + ssh_only = self.ssh_only_check.isChecked() + self.vm_name_edit.setEnabled(not ssh_only) + if ssh_only: + self.vm_name_edit.setPlaceholderText("纯 SSH 模式无需虚拟机名称") + else: + self.vm_name_edit.setPlaceholderText("例如: ubuntu20.04") + + def get_config(self) -> dict: + return { + "vm_name": self.vm_name_edit.text().strip(), + "ssh_host": self.ssh_host_edit.text().strip(), + "ssh_port": self.ssh_port_spin.value(), + "ssh_user": self.ssh_user_edit.text().strip(), + "ssh_pass": self.ssh_pass_edit.text().strip(), + "api_key": self.api_key_edit.text().strip(), + "base_url": self.base_url_edit.text().strip(), + "model": self.model_edit.text().strip(), + "ssh_only": self.ssh_only_check.isChecked(), + } + + def validate(self) -> bool: + cfg = self.get_config() + missing = [] + if not cfg["ssh_only"] and not cfg["vm_name"]: + missing.append("虚拟机名称") + if not cfg["ssh_host"]: + missing.append("SSH 主机") + if not cfg["ssh_user"]: + missing.append("SSH 用户") + if not cfg["ssh_pass"]: + missing.append("SSH 密码") + if not cfg["api_key"]: + missing.append("API Key") + if missing: + QMessageBox.warning( + self, + "配置不完整", + "请填写以下字段:\n" + "\n".join(f" - {m}" for m in missing), + ) + return False + return True + + def set_enabled(self, enabled: bool): + self.ssh_only_check.setEnabled(enabled) + self.vm_name_edit.setEnabled(enabled and not self.ssh_only_check.isChecked()) + self.ssh_host_edit.setEnabled(enabled) + self.ssh_port_spin.setEnabled(enabled) + self.ssh_user_edit.setEnabled(enabled) + self.ssh_pass_edit.setEnabled(enabled) + self.api_key_edit.setEnabled(enabled) + self.base_url_edit.setEnabled(enabled) + self.model_edit.setEnabled(enabled) + self.load_env_btn.setEnabled(enabled) + self.save_config_btn.setEnabled(enabled) + + +class UploadWorker(QObject): + """后台线程执行文件上传(支持 VirtualBox 和纯 SSH 模式)""" + + log_signal = Signal(str, str) + finished_signal = Signal(bool) + error_signal = Signal(str) + + def __init__(self, config: dict, local_path: str, remote_path: str): + super().__init__() + self.config = config + self.local_path = local_path + self.remote_path = remote_path + self.cancel_event = threading.Event() + + def cancel(self): + self.cancel_event.set() + + def run(self): + ssh_only = self.config.get("ssh_only", False) + _com_initialized = False + if not ssh_only: + try: + import ctypes + + ctypes.windll.ole32.CoInitializeEx(None, 0) + _com_initialized = True + except Exception: + pass + + try: + if ssh_only: + from virtualbox_client.utils.ssh_manager import SSHManager + + vm = SSHManager( + self.config["ssh_host"], + self.config["ssh_port"], + self.config["ssh_user"], + self.config["ssh_pass"], + ) + else: + from virtualbox_client.utils.vbox_ssh_manager import VBoxSSHManager + + vm = VBoxSSHManager( + self.config["vm_name"], + self.config["ssh_host"], + self.config["ssh_port"], + self.config["ssh_user"], + self.config["ssh_pass"], + ) + vm.set_cancel_event(self.cancel_event) + + if self.cancel_event.is_set(): + return + + self.log_signal.emit( + f"📤 正在上传: {self.local_path} -> {self.remote_path}", "info" + ) + original_print = print + import builtins + + def custom_print(*args, **kwargs): + msg = " ".join(str(a) for a in args) + self.log_signal.emit(msg, "info") + original_print(*args, **kwargs) + + builtins.print = custom_print + + try: + success = vm.upload_file(self.local_path, self.remote_path) + if success: + self.log_signal.emit("✅ 文件上传成功", "success") + self.finished_signal.emit(True) + else: + self.log_signal.emit("❌ 文件上传失败", "error") + self.finished_signal.emit(False) + finally: + builtins.print = original_print + + except ImportError as e: + self.log_signal.emit(f"❌ 缺少依赖: {e}", "error") + self.error_signal.emit(str(e)) + except Exception as e: + self.log_signal.emit(f"❌ 上传错误: {str(e)}", "error") + self.error_signal.emit(str(e)) + finally: + if _com_initialized: + try: + import ctypes + + ctypes.windll.ole32.CoUninitialize() + except Exception: + pass + + +class DownloadWorker(QObject): + """后台线程执行文件下载(支持 VirtualBox 和纯 SSH 模式)""" + + log_signal = Signal(str, str) + finished_signal = Signal(bool) + error_signal = Signal(str) + + def __init__(self, config: dict, remote_path: str, local_path: str): + super().__init__() + self.config = config + self.remote_path = remote_path + self.local_path = local_path + self.cancel_event = threading.Event() + + def cancel(self): + self.cancel_event.set() + + def run(self): + ssh_only = self.config.get("ssh_only", False) + _com_initialized = False + if not ssh_only: + try: + import ctypes + + ctypes.windll.ole32.CoInitializeEx(None, 0) + _com_initialized = True + except Exception: + pass + + try: + if ssh_only: + from virtualbox_client.utils.ssh_manager import SSHManager + + vm = SSHManager( + self.config["ssh_host"], + self.config["ssh_port"], + self.config["ssh_user"], + self.config["ssh_pass"], + ) + else: + from virtualbox_client.utils.vbox_ssh_manager import VBoxSSHManager + + vm = VBoxSSHManager( + self.config["vm_name"], + self.config["ssh_host"], + self.config["ssh_port"], + self.config["ssh_user"], + self.config["ssh_pass"], + ) + vm.set_cancel_event(self.cancel_event) + + if self.cancel_event.is_set(): + return + + self.log_signal.emit( + f"📥 正在下载: {self.remote_path} -> {self.local_path}", "info" + ) + original_print = print + import builtins + + def custom_print(*args, **kwargs): + msg = " ".join(str(a) for a in args) + self.log_signal.emit(msg, "info") + original_print(*args, **kwargs) + + builtins.print = custom_print + + try: + success = vm.download_file(self.remote_path, self.local_path) + if success: + self.log_signal.emit("✅ 文件下载成功", "success") + self.finished_signal.emit(True) + else: + self.log_signal.emit("❌ 文件下载失败", "error") + self.finished_signal.emit(False) + finally: + builtins.print = original_print + + except ImportError as e: + self.log_signal.emit(f"❌ 缺少依赖: {e}", "error") + self.error_signal.emit(str(e)) + except Exception as e: + self.log_signal.emit(f"❌ 下载错误: {str(e)}", "error") + self.error_signal.emit(str(e)) + finally: + if _com_initialized: + try: + import ctypes + + ctypes.windll.ole32.CoUninitialize() + except Exception: + pass + + +class ScriptWorker(QObject): + log_signal = Signal(str, str) + finished_signal = Signal(bool) + error_signal = Signal(str) + + def __init__(self, config: dict, local_path: str): + super().__init__() + self.config = config + self.local_path = local_path + self.cancel_event = threading.Event() + + def cancel(self): + self.cancel_event.set() + + def run(self): + ssh_only = self.config.get("ssh_only", False) + _com_initialized = False + if not ssh_only: + try: + import ctypes + + ctypes.windll.ole32.CoInitializeEx(None, 0) + _com_initialized = True + except Exception: + pass + + try: + if ssh_only: + from virtualbox_client.utils.ssh_manager import SSHManager + + vm = SSHManager( + self.config["ssh_host"], + self.config["ssh_port"], + self.config["ssh_user"], + self.config["ssh_pass"], + ) + else: + from virtualbox_client.utils.vbox_ssh_manager import VBoxSSHManager + + vm = VBoxSSHManager( + self.config["vm_name"], + self.config["ssh_host"], + self.config["ssh_port"], + self.config["ssh_user"], + self.config["ssh_pass"], + ) + vm.set_cancel_event(self.cancel_event) + + if self.cancel_event.is_set(): + return + + # 非纯 SSH 模式下,先确保虚拟机已启动 + if not ssh_only: + self.log_signal.emit("🚀 检查虚拟机状态...", "info") + vm_started = vm.start_vm(wait_for_ssh=True) + if not vm_started: + self.log_signal.emit("❌ 虚拟机启动失败", "error") + self.error_signal.emit("虚拟机启动失败") + return + self.log_signal.emit("✅ 虚拟机已就绪", "success") + if self.cancel_event.is_set(): + return + + import builtins + + original_print = print + + def custom_print(*args, **kwargs): + msg = " ".join(str(a) for a in args) + self.log_signal.emit(msg, "info") + original_print(*args, **kwargs) + + builtins.print = custom_print + + try: + import os as _os + + script_name = _os.path.basename(self.local_path) + remote_path = "/tmp/" + script_name + log_path = "/tmp/" + script_name + ".log" + local_log_path = script_name + ".log" + + self.log_signal.emit( + "[1/3] Upload: " + self.local_path + " -> " + remote_path, "info" + ) + if not vm.upload_file(self.local_path, remote_path): + self.log_signal.emit("Upload failed", "error") + return + + self.log_signal.emit("[2/3] Execute: " + remote_path, "info") + # deploy_* 脚本需要 sudo 权限(系统级安装),其余脚本以普通用户执行 + if script_name.startswith("deploy_"): + cmd = ( + "chmod +x " + remote_path + " && " + "echo '" + + self.config["ssh_pass"] + + "' | sudo -S " + + remote_path + + " 2>&1 | " + "tee " + log_path + ) + self.log_signal.emit( + " 🔑 检测到部署脚本,以 sudo 权限执行", "info" + ) + else: + cmd = ( + "chmod +x " + remote_path + " && " + remote_path + " 2>&1 | " + "tee " + log_path + ) + self.log_signal.emit( + " 👤 检测到开发脚本,以普通用户权限执行", "info" + ) + out, err, code = vm.exec_command(cmd, get_pty=False) + self.log_signal.emit("Exit code: " + str(code), "info") + + self.log_signal.emit( + "[3/3] Download log: " + log_path + " -> " + local_log_path, "info" + ) + if vm.download_file(log_path, local_log_path): + self.log_signal.emit("Log saved: " + local_log_path, "success") + else: + self.log_signal.emit( + "Tee log download failed, saving terminal output", "warning" + ) + if out: + with open(local_log_path, "w") as f: + f.write(out) + self.log_signal.emit( + "Output saved: " + local_log_path, "success" + ) + + vm.exec_command('rm -f "' + remote_path + '" "' + log_path + '"') + self.log_signal.emit("Remote temp files cleaned", "info") + self.log_signal.emit("Script execution complete!", "success") + self.finished_signal.emit(True) + finally: + builtins.print = original_print + + except ImportError as e: + self.log_signal.emit("Missing dependency: " + str(e), "error") + self.error_signal.emit(str(e)) + except Exception as e: + self.log_signal.emit("Script error: " + str(e), "error") + self.error_signal.emit(str(e)) + finally: + if _com_initialized: + try: + import ctypes + + ctypes.windll.ole32.CoUninitialize() + except Exception: + pass + + +class WorkspaceWorker(QObject): + """后台线程执行工作区文件操作(列表、删除、复制、重命名、创建目录)""" + + log_signal = Signal(str, str) + finished_signal = Signal(object) + error_signal = Signal(str) + + OPERATION_LIST = "list" + OPERATION_DELETE = "delete" + OPERATION_COPY = "copy" + OPERATION_RENAME = "rename" + OPERATION_MKDIR = "mkdir" + OPERATION_UPLOAD = "upload" + OPERATION_UPLOAD_DIR = "upload_dir" + + def __init__(self, config: dict, operation: str, **kwargs): + super().__init__() + self.config = config + self.operation = operation + self.kwargs = kwargs + self.cancel_event = threading.Event() + + def cancel(self): + self.cancel_event.set() + + def run(self): + ssh_only = self.config.get("ssh_only", False) + _com_initialized = False + if not ssh_only: + try: + import ctypes + + ctypes.windll.ole32.CoInitializeEx(None, 0) + _com_initialized = True + except Exception: + pass + + try: + if ssh_only: + from virtualbox_client.utils.ssh_manager import SSHManager + + vm = SSHManager( + self.config["ssh_host"], + self.config["ssh_port"], + self.config["ssh_user"], + self.config["ssh_pass"], + ) + else: + from virtualbox_client.utils.vbox_ssh_manager import VBoxSSHManager + + vm = VBoxSSHManager( + self.config["vm_name"], + self.config["ssh_host"], + self.config["ssh_port"], + self.config["ssh_user"], + self.config["ssh_pass"], + ) + vm.set_cancel_event(self.cancel_event) + + if self.cancel_event.is_set(): + return + + if self.operation == WorkspaceWorker.OPERATION_LIST: + dir_path = self.kwargs.get("dir_path", "~/workspace") + result = self._list_files(vm, dir_path) + elif self.operation == WorkspaceWorker.OPERATION_DELETE: + result = self._delete_file(vm) + elif self.operation == WorkspaceWorker.OPERATION_COPY: + result = self._copy_file(vm) + elif self.operation == WorkspaceWorker.OPERATION_RENAME: + result = self._rename_file(vm) + elif self.operation == WorkspaceWorker.OPERATION_MKDIR: + result = self._mkdir(vm) + elif self.operation == WorkspaceWorker.OPERATION_UPLOAD: + result = self._upload_file(vm) + elif self.operation == WorkspaceWorker.OPERATION_UPLOAD_DIR: + result = self._upload_directory(vm) + else: + raise ValueError(f"未知操作: {self.operation}") + + if self.cancel_event.is_set(): + return + + self.finished_signal.emit(result) + + except ImportError as e: + self.log_signal.emit(f"❌ 缺少依赖: {e}", "error") + self.error_signal.emit(str(e)) + except Exception as e: + self.log_signal.emit(f"❌ 工作区操作失败: {str(e)}", "error") + self.error_signal.emit(str(e)) + finally: + if _com_initialized: + try: + import ctypes + + ctypes.windll.ole32.CoUninitialize() + except Exception: + pass + + def _ensure_workspace(self, vm) -> bool: + out, err, code = vm.exec_command("mkdir -p ~/workspace") + return code == 0 + + def _list_files(self, vm, dir_path: str = "~/workspace") -> list: + """列出指定目录中的文件,返回文件信息列表。""" + import base64 + import json as json_mod + + # 对包含 ~ 的路径使用 shell 展开创建目录 + vm.exec_command(f"mkdir -p {dir_path}") + self.log_signal.emit(f"📂 正在扫描: {dir_path}", "info") + + py_script = ( + "import os,sys,json,time\n" + 'ws=os.path.expanduser("' + dir_path.replace('"', '\\"') + '")\n' + "files=[]\n" + "try:\n" + " for f in sorted(os.listdir(ws),key=lambda x:(not os.path.isdir(os.path.join(ws,x)),x.lower())):\n" + " if f.startswith('.'):\n" + " continue\n" + " fp=os.path.join(ws,f)\n" + " try:\n" + " s=os.stat(fp)\n" + " files.append({'name':f,'size':s.st_size,'is_dir':os.path.isdir(fp),'modified':int(s.st_mtime),'path':fp})\n" + " except:\n" + " pass\n" + "except Exception as e:\n" + " sys.stderr.write(str(e))\n" + "print(json.dumps(files))\n" + ) + encoded = base64.b64encode(py_script.encode()).decode() + + out, err, code = vm.exec_command( + f'echo "{encoded}" | base64 -d 2>/dev/null | python3 2>/dev/null' + ) + + if code != 0 or not out or not out.strip(): + out, err, code = vm.exec_command( + f'echo "{encoded}" | base64 -d 2>/dev/null | python 2>/dev/null' + ) + if code != 0 or not out or not out.strip(): + self.log_signal.emit( + "⚠️ Python 不可用,使用 find 命令列出文件", "warning" + ) + # 展开 ~ 为真实路径(find 命令本身不会展开 ~) + resolved_dir = dir_path + if resolved_dir.startswith("~/"): + home_out, _, _ = vm.exec_command("echo ~") + if home_out and home_out.strip(): + resolved_dir = home_out.strip() + resolved_dir[1:] + out2, err2, code2 = vm.exec_command( + f'find {resolved_dir} -maxdepth 1 ! -name "." ! -name ".." -printf "%f|%y|%s\\n" 2>/dev/null | sort' + ) + if code2 != 0 or not out2 or not out2.strip(): + self.log_signal.emit(f"ℹ️ 目录为空或不可访问: {dir_path}", "info") + return [] + + files = [] + for line in out2.strip().split("\n"): + line = line.strip() + if not line: + continue + parts = line.split("|") + if len(parts) < 3: + continue + name, ftype, size_str = parts[0], parts[1], parts[2] + try: + size = int(size_str) + except ValueError: + size = 0 + files.append( + { + "name": name, + "size": size, + "is_dir": ftype == "d", + "modified": "", + "path": f"{resolved_dir}/{name}", + } + ) + return files + + try: + files = json_mod.loads(out.strip()) + for f in files: + f.setdefault("name", "未知") + f.setdefault("size", 0) + f.setdefault("is_dir", False) + f.setdefault("modified", "") + f.setdefault("path", f"~/workspace/{f.get('name', '')}") + if isinstance(f.get("modified"), (int, float)) and f["modified"]: + try: + f["modified"] = datetime.fromtimestamp(f["modified"]).strftime( + "%Y-%m-%d %H:%M:%S" + ) + except Exception: + f["modified"] = str(f["modified"]) + self.log_signal.emit( + f"📂 扫描完成: 找到 {len(files)} 个文件/文件夹", "info" + ) + return files + except (json_mod.JSONDecodeError, Exception) as e: + self.log_signal.emit(f"❌ 解析文件列表失败: {e}", "error") + return [] + + def _delete_file(self, vm) -> bool: + paths = self.kwargs.get("path", []) + if not paths: + return False + if isinstance(paths, str): + paths = [paths] + # 使用空格分隔拼接多个路径,统一用双引号包裹以支持含空格的路径 + joined = " ".join(f'"{p}"' for p in paths) + out, err, code = vm.exec_command(f"rm -rf {joined}") + return code == 0 + + def _copy_file(self, vm) -> bool: + src = self.kwargs.get("src", "") + dst = self.kwargs.get("dst", "") + if not src or not dst: + return False + out, err, code = vm.exec_command(f'cp -r "{src}" "{dst}"') + return code == 0 + + def _rename_file(self, vm) -> bool: + src = self.kwargs.get("src", "") + dst = self.kwargs.get("dst", "") + if not src or not dst: + return False + out, err, code = vm.exec_command(f'mv "{src}" "{dst}"') + return code == 0 + + def _mkdir(self, vm) -> bool: + name = self.kwargs.get("name", "") + if not name: + return False + out, err, code = vm.exec_command(f"mkdir -p ~/workspace/{name}") + return code == 0 + + def _upload_file(self, vm) -> bool: + local_path = self.kwargs.get("local_path", "") + remote_path = self.kwargs.get("remote_path", "") + if not local_path or not remote_path: + return False + remote_dir = ( + remote_path.rsplit("/", 1)[0] + if "/" in remote_path + else remote_path.rsplit("\\", 1)[0] + ) + vm.exec_command(f'mkdir -p "{remote_dir}"') + return vm.upload_file(local_path, remote_path) + + def _upload_directory(self, vm) -> bool: + local_dir = self.kwargs.get("local_path", "") + remote_dir = self.kwargs.get("remote_path", "") + if not local_dir or not remote_dir: + return False + return vm.upload_directory(local_dir, remote_dir) + + +class FileSizeFormatter: + """文件大小格式化工具""" + + @staticmethod + def format(size_bytes: int) -> str: + if size_bytes < 1024: + return f"{size_bytes} B" + elif size_bytes < 1024 * 1024: + return f"{size_bytes / 1024:.1f} KB" + elif size_bytes < 1024 * 1024 * 1024: + return f"{size_bytes / (1024 * 1024):.1f} MB" + else: + return f"{size_bytes / (1024 * 1024 * 1024):.2f} GB" + + +class WorkspacePanel(QWidget): + """工作区面板 - 树形文件浏览器,双击文件夹展开子目录,单击显示文件信息""" + + def __init__(self, config_panel: ConfigPanel, log_panel: "LogPanel"): + super().__init__() + self._config_panel = config_panel + self.log_panel = log_panel + self._busy = False + self._loaded_dirs = set() # 已加载的目录路径 + self._worker_thread = None + self._worker = None + self._pending_expand_item = None # 待展开的树节点 + self._current_info_item = None # 当前显示详细信息的项 + self._download_worker_thread = None + self._download_worker = None + + self.setup_ui() + + def setup_ui(self): + layout = QVBoxLayout(self) + layout.setContentsMargins(8, 8, 8, 8) + + # ---- 工具栏 ---- + toolbar = QHBoxLayout() + + self.refresh_btn = QPushButton("🔄 刷新") + self.refresh_btn.setStyleSheet(""" + QPushButton { background-color: #0e639c; color: white; padding: 6px 14px; + font-size: 12px; font-weight: bold; border: none; border-radius: 4px; } + QPushButton:hover { background-color: #1177bb; } + QPushButton:disabled { background-color: #555; color: #999; } + """) + self.refresh_btn.clicked.connect(self.refresh_root) + toolbar.addWidget(self.refresh_btn) + + self.upload_btn = QPushButton("📤 上传文件") + self.upload_btn.setStyleSheet(""" + QPushButton { background-color: #2d7d46; color: white; padding: 6px 14px; + font-size: 12px; font-weight: bold; border: none; border-radius: 4px; } + QPushButton:hover { background-color: #389e54; } + QPushButton:disabled { background-color: #555; color: #999; } + """) + self.upload_btn.clicked.connect(self._upload_to_workspace) + toolbar.addWidget(self.upload_btn) + + self.upload_dir_btn = QPushButton("📁 上传文件夹") + self.upload_dir_btn.setStyleSheet(""" + QPushButton { background-color: #16a085; color: white; padding: 6px 14px; + font-size: 12px; font-weight: bold; border: none; border-radius: 4px; } + QPushButton:hover { background-color: #1abc9c; } + QPushButton:disabled { background-color: #555; color: #999; } + """) + self.upload_dir_btn.clicked.connect(self._upload_folder_to_workspace) + toolbar.addWidget(self.upload_dir_btn) + + self.mkdir_btn = QPushButton("📂 新建文件夹") + self.mkdir_btn.setStyleSheet(""" + QPushButton { background-color: #e67e22; color: white; padding: 6px 14px; + font-size: 12px; font-weight: bold; border: none; border-radius: 4px; } + QPushButton:hover { background-color: #f39c12; } + QPushButton:disabled { background-color: #555; color: #999; } + """) + self.mkdir_btn.clicked.connect(self._create_folder) + toolbar.addWidget(self.mkdir_btn) + + self.home_btn = QPushButton("🏠 回到根目录") + self.home_btn.setStyleSheet(""" + QPushButton { background-color: #6c5ce7; color: white; padding: 6px 14px; + font-size: 12px; font-weight: bold; border: none; border-radius: 4px; } + QPushButton:hover { background-color: #7c6cf7; } + QPushButton:disabled { background-color: #555; color: #999; } + """) + self.home_btn.clicked.connect(self._go_home) + toolbar.addWidget(self.home_btn) + + toolbar.addStretch() + + self.status_label = QLabel("就绪") + self.status_label.setStyleSheet("color: #888; font-size: 12px;") + toolbar.addWidget(self.status_label) + + layout.addLayout(toolbar) + + # ---- 文件树(真正的树状结构) ---- + self.file_tree = QTreeWidget() + self.file_tree.setHeaderLabels(["名称", "大小", "类型", "修改时间", "权限"]) + self.file_tree.setColumnWidth(0, 280) + self.file_tree.setColumnWidth(1, 100) + self.file_tree.setColumnWidth(2, 80) + self.file_tree.setColumnWidth(3, 160) + self.file_tree.setColumnWidth(4, 80) + self.file_tree.setAlternatingRowColors(True) + self.file_tree.setSelectionMode( + QAbstractItemView.SelectionMode.ExtendedSelection + ) + self.file_tree.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) + self.file_tree.customContextMenuRequested.connect(self._show_context_menu) + self.file_tree.setRootIsDecorated(True) + self.file_tree.setAnimated(True) + + # 双击展开/收起文件夹(懒加载) + self.file_tree.itemDoubleClicked.connect(self._on_item_double_clicked) + # 单击显示文件信息 + self.file_tree.itemClicked.connect(self._on_item_clicked) + + self.file_tree.setStyleSheet(""" + QTreeWidget { + background-color: #1e1e1e; color: #d4d4d4; + border: 1px solid #3c3c3c; border-radius: 4px; + alternate-background-color: #2a2a2a; + font-size: 13px; + } + QTreeWidget::item:hover { background-color: #2d2d2d; } + QTreeWidget::item:selected { background-color: #094771; } + QTreeWidget::branch:has-children:closed:has-siblings { border-image: none; } + QTreeWidget::branch:open:has-children:has-siblings { border-image: none; } + QHeaderView::section { + background-color: #2d2d2d; color: #cccccc; + padding: 6px; border: none; border-bottom: 1px solid #3c3c3c; + font-weight: bold; + } + """) + + layout.addWidget(self.file_tree) + + # ---- 文件详细信息面板(单击文件时显示) ---- + self.info_panel = QFrame() + self.info_panel.setFrameShape(QFrame.Shape.StyledPanel) + self.info_panel.setStyleSheet(""" + QFrame { + background-color: #1e1e1e; + border: 1px solid #3c3c3c; + border-radius: 4px; + padding: 8px; + } + QLabel { color: #d4d4d4; font-size: 12px; padding: 2px; } + """) + info_layout = QVBoxLayout(self.info_panel) + info_layout.setContentsMargins(8, 4, 8, 4) + self.info_label = QLabel("选择文件以查看详情") + self.info_label.setStyleSheet( + "color: #888; font-style: italic; font-size: 12px;" + ) + info_layout.addWidget(self.info_label) + self.info_panel.setMaximumHeight(60) + self.info_panel.hide() + layout.addWidget(self.info_panel) + + # ---- 进度条 ---- + self.progress_bar = QProgressBar() + self.progress_bar.setRange(0, 0) + self.progress_bar.setMaximumHeight(4) + self.progress_bar.setTextVisible(False) + self.progress_bar.hide() + layout.addWidget(self.progress_bar) + + # ---- 线程管理 ---- + + def _cleanup_worker(self): + if self._worker_thread: + try: + self._worker_thread.finished.disconnect() + except TypeError, RuntimeError: + pass + if self._worker_thread.isRunning(): + self._worker_thread.quit() + if not self._worker_thread.wait(5000): + self._worker_thread.terminate() + self._worker_thread.wait(3000) + self._worker_thread = None + if self._worker: + try: + self._worker.log_signal.disconnect() + self._worker.finished_signal.disconnect() + self._worker.error_signal.disconnect() + except TypeError, RuntimeError: + pass + self._worker = None + + def _set_busy(self, busy: bool, status_text: str = ""): + self._busy = busy + self.refresh_btn.setEnabled(not busy) + self.upload_btn.setEnabled(not busy) + self.upload_dir_btn.setEnabled(not busy) + self.mkdir_btn.setEnabled(not busy) + self.home_btn.setEnabled(not busy) + self.file_tree.setEnabled(not busy) + if busy: + self.progress_bar.show() + self.status_label.setText(status_text or "操作中...") + else: + self.progress_bar.hide() + self.status_label.setText(status_text or "就绪") + + def _validate_ssh_config(self) -> bool: + if not self._config_panel: + return False + cfg = self._config_panel.get_config() + missing = [] + if not cfg["ssh_host"]: + missing.append("SSH 主机") + if not cfg["ssh_user"]: + missing.append("SSH 用户") + if not cfg["ssh_pass"]: + missing.append("SSH 密码") + if missing: + self.log_panel.append_log( + "❌ SSH 配置不完整: " + ", ".join(missing), "error" + ) + return False + return True + + def _start_worker(self, operation: str, **kwargs): + if not self._config_panel or not self._validate_ssh_config(): + self._set_busy(False, "SSH 配置不完整") + return + + self._cleanup_worker() + self._set_busy(True, "正在连接...") + + config = self._config_panel.get_config() + self._worker = WorkspaceWorker(config, operation, **kwargs) + self._worker_thread = QThread(self) + self._worker.moveToThread(self._worker_thread) + + self._worker.log_signal.connect(self.log_panel.append_log) + self._worker.finished_signal.connect(self._on_worker_finished) + self._worker.error_signal.connect(self._on_worker_error) + self._worker_thread.started.connect(self._worker.run) + self._worker_thread.finished.connect(self._cleanup_worker) + + self._worker_thread.start() + + def _on_worker_finished(self, result): + self._set_busy(False, "就绪") + + if self._worker and self._worker.operation == WorkspaceWorker.OPERATION_LIST: + if self._pending_expand_item: + # 展开子目录的结果 + self._apply_directory_children(self._pending_expand_item, result) + self._pending_expand_item = None + else: + # 根目录刷新结果 + self._refresh_root_tree(result) + count = len(result) if isinstance(result, list) else 0 + self.log_panel.append_log(f"📂 共 {count} 个文件/文件夹", "info") + elif self._worker and self._worker.operation in ( + WorkspaceWorker.OPERATION_UPLOAD, + WorkspaceWorker.OPERATION_UPLOAD_DIR, + ): + if result: + self.log_panel.append_log("✅ 上传成功", "success") + else: + self.log_panel.append_log("❌ 上传失败", "error") + QTimer.singleShot(500, self._soft_refresh) + else: + if result: + self.log_panel.append_log("✅ 操作成功", "success") + else: + self.log_panel.append_log("❌ 操作失败", "error") + QTimer.singleShot(500, self._soft_refresh) + + def _on_worker_error(self, error_msg): + self._set_busy(False, "错误") + self.log_panel.append_log(f"❌ 工作区错误: {error_msg}", "error") + self._pending_expand_item = None + + # ---- 树状结构核心逻辑 ---- + + def refresh_root(self): + """刷新根目录""" + self._loaded_dirs.clear() + self._pending_expand_item = None + self.file_tree.clear() + self.info_panel.hide() + self._start_worker(WorkspaceWorker.OPERATION_LIST, dir_path="~/workspace") + + def _soft_refresh(self): + """刷新后保持展开状态""" + self._loaded_dirs.clear() + self._pending_expand_item = None + self.file_tree.clear() + self.info_panel.hide() + self._start_worker(WorkspaceWorker.OPERATION_LIST, dir_path="~/workspace") + + def _go_home(self): + """回到根目录:收起所有节点,仅显示根目录""" + self.file_tree.collapseAll() + self.info_panel.hide() + + def _refresh_root_tree(self, files: list): + """用文件列表填充根树""" + if not isinstance(files, list): + return + self.file_tree.clear() + + for f in files: + item = self._create_tree_item(f) + self.file_tree.addTopLevelItem(item) + + self.file_tree.sortItems(0, Qt.SortOrder.AscendingOrder) + + def _apply_directory_children(self, parent_item: QTreeWidgetItem, files: list): + """给一个已展开的文件夹项填充子节点""" + if not isinstance(files, list): + return + + # 移除旧的占位符 + while parent_item.childCount() > 0: + child = parent_item.child(0) + if child and child.data(0, Qt.ItemDataRole.UserRole + 10) == "placeholder": + parent_item.removeChild(child) + else: + break + + # 再清空所有子节点(因为已经懒加载过了) + parent_item.takeChildren() + + # 添加新的子节点 + for f in files: + child_item = self._create_tree_item(f) + # 如果是文件夹,添加一个占位子节点使其可展开 + if f.get("is_dir", False): + placeholder = QTreeWidgetItem() + placeholder.setText(0, "") + placeholder.setData(0, Qt.ItemDataRole.UserRole + 10, "placeholder") + child_item.addChild(placeholder) + parent_item.addChild(child_item) + + parent_item.setExpanded(True) + self.file_tree.sortItems(0, Qt.SortOrder.AscendingOrder) + + def _create_tree_item(self, f: dict) -> QTreeWidgetItem: + """根据文件信息创建树节点项""" + name = f.get("name", "未知") + is_dir = f.get("is_dir", False) + size = f.get("size", 0) + modified = f.get("modified", "") + perm = f.get("permissions", "") + + if isinstance(modified, (int, float)): + try: + modified = datetime.fromtimestamp(modified).strftime( + "%Y-%m-%d %H:%M:%S" + ) + except Exception: + modified = str(modified) + + item = QTreeWidgetItem() + item.setText(0, name) + item.setData(0, Qt.ItemDataRole.UserRole, f.get("path", "")) + item.setData(0, Qt.ItemDataRole.UserRole + 1, is_dir) + + if is_dir: + item.setText(1, "—") + item.setText(2, "📁 文件夹") + item.setForeground(0, QBrush(QColor("#569cd6"))) + # 添加一个占位子节点使其可展开 + placeholder = QTreeWidgetItem() + placeholder.setText(0, "") + placeholder.setData(0, Qt.ItemDataRole.UserRole + 10, "placeholder") + item.addChild(placeholder) + else: + item.setText(1, FileSizeFormatter.format(size)) + ext = os.path.splitext(name)[1].lower() + type_map = { + ".py": "🐍 Python", + ".js": "🟨 JavaScript", + ".ts": "🔵 TypeScript", + ".html": "🌐 HTML", + ".css": "🎨 CSS", + ".json": "📋 JSON", + ".txt": "📄 文本", + ".md": "📝 Markdown", + ".yaml": "⚙️ YAML", + ".yml": "⚙️ YAML", + ".sh": "⚡ Shell", + ".zip": "📦 ZIP", + ".tar": "📦 TAR", + ".gz": "📦 GZip", + ".png": "🖼️ 图片", + ".jpg": "🖼️ 图片", + ".jpeg": "🖼️ 图片", + ".gif": "🖼️ GIF", + ".svg": "🖼️ SVG", + ".pdf": "📕 PDF", + ".log": "📋 日志", + ".conf": "⚙️ 配置", + ".env": "🔒 环境", + ".toml": "⚙️ TOML", + ".xml": "📋 XML", + ".lock": "🔒 锁定", + } + item.setText(2, type_map.get(ext, "📄 文件")) + item.setForeground(0, QBrush(QColor("#d4d4d4"))) + + item.setText(3, str(modified)) + item.setText(4, perm) + + return item + + def _on_item_double_clicked(self, item: QTreeWidgetItem, column: int): + """双击节点:文件夹展开/收起懒加载,文件显示信息""" + is_dir = item.data(0, Qt.ItemDataRole.UserRole + 1) + path = item.data(0, Qt.ItemDataRole.UserRole) + + if not is_dir: + # 双击文件显示详细信息 + self._show_file_info(item) + return + + if not path: + return + + # 如果已展开,直接折叠 + if item.isExpanded(): + item.setExpanded(False) + return + + # 检查是否已加载过子目录(通过是否有真实子节点判断) + has_real_children = False + for i in range(item.childCount()): + child = item.child(i) + if child and child.data(0, Qt.ItemDataRole.UserRole + 10) != "placeholder": + has_real_children = True + break + + if has_real_children: + item.setExpanded(True) + return + + # 懒加载:从远程获取子目录内容 + self.log_panel.append_log(f"📂 正在展开: {path}", "info") + self._pending_expand_item = item + self._start_worker(WorkspaceWorker.OPERATION_LIST, dir_path=path) + + def _on_item_clicked(self, item: QTreeWidgetItem, column: int): + """单击节点显示文件信息""" + self._show_file_info(item) + + def _show_file_info(self, item: QTreeWidgetItem): + """显示文件/文件夹的详细信息""" + name = item.text(0) + is_dir = item.data(0, Qt.ItemDataRole.UserRole + 1) + path = item.data(0, Qt.ItemDataRole.UserRole) or "" + + size = item.text(1) + ftype = item.text(2) + modified = item.text(3) + perm = item.text(4) + + if is_dir: + child_count = 0 + for i in range(item.childCount()): + child = item.child(i) + if ( + child + and child.data(0, Qt.ItemDataRole.UserRole + 10) != "placeholder" + ): + child_count += 1 + info_parts = [ + f"📁 **{name}**", + f"路径: {path}", + f"类型: 文件夹", + f"大小: {size}", + f"修改时间: {modified}" if modified else "", + ] + info_text = " | ".join(p for p in info_parts if p) + else: + info_parts = [ + f"📄 **{name}**", + f"路径: {path}", + f"类型: {ftype}", + f"大小: {size}", + f"修改时间: {modified}" if modified else "", + ] + info_text = " | ".join(p for p in info_parts if p) + + self.info_label.setText(f"📌 {info_text}") + self.info_panel.show() + self._current_info_item = item + + # ---- 右键菜单 ---- + + def _get_selected_paths(self) -> list: + paths = [] + for item in self.file_tree.selectedItems(): + path = item.data(0, Qt.ItemDataRole.UserRole) + if path: + paths.append(path) + return paths + + def _get_selected_names(self) -> list: + names = [] + for item in self.file_tree.selectedItems(): + names.append(item.text(0)) + return names + + def _show_context_menu(self, pos): + items = self.file_tree.selectedItems() + if not items: + return + + menu = QMenu(self) + menu.setStyleSheet(""" + QMenu { background-color: #2d2d2d; color: #ccc; border: 1px solid #3c3c3c; padding: 4px; } + QMenu::item { padding: 6px 24px; border-radius: 3px; } + QMenu::item:selected { background-color: #094771; } + QMenu::separator { height: 1px; background: #3c3c3c; margin: 4px 8px; } + """) + + if len(items) == 1: + item = items[0] + name = item.text(0) + is_dir = item.data(0, Qt.ItemDataRole.UserRole + 1) + + rename_action = QAction(f'✏️ 重命名 "{name}"', self) + rename_action.triggered.connect(lambda: self._rename_item(item)) + menu.addAction(rename_action) + + copy_action = QAction(f'📋 复制 "{name}"', self) + copy_action.triggered.connect(lambda: self._copy_item(item)) + menu.addAction(copy_action) + + if is_dir: + download_action = QAction("📥 下载文件夹(打包为 zip)", self) + else: + download_action = QAction("📥 下载文件", self) + download_action.triggered.connect(lambda: self._download_item(item)) + menu.addAction(download_action) + + menu.addSeparator() + + delete_action = QAction(f"🗑️ 删除选中的 {len(items)} 个项目", self) + delete_action.triggered.connect(self._delete_selected) + menu.addAction(delete_action) + + menu.exec(self.file_tree.viewport().mapToGlobal(pos)) + + # ---- 文件操作 ---- + + def _rename_item(self, item): + old_name = item.text(0) + new_name, ok = QInputDialog.getText( + self, "重命名", "请输入新名称:", text=old_name + ) + if not ok or not new_name.strip() or new_name.strip() == old_name: + return + + src_path = item.data(0, Qt.ItemDataRole.UserRole) + if not src_path: + return + + dst_dir = ( + src_path.rsplit("/", 1)[0] + if "/" in src_path + else src_path.rsplit("\\", 1)[0] + ) + dst_path = dst_dir.rstrip("/") + "/" + new_name.strip() + + self._start_worker( + WorkspaceWorker.OPERATION_RENAME, + src=src_path, + dst=dst_path, + ) + + def _copy_item(self, item): + src_path = item.data(0, Qt.ItemDataRole.UserRole) + name = item.text(0) + base, ext = os.path.splitext(name) + new_name = f"{base}_copy{ext}" + dst_dir = ( + src_path.rsplit("/", 1)[0] + if "/" in src_path + else src_path.rsplit("\\", 1)[0] + ) + dst_path = dst_dir.rstrip("/") + "/" + new_name + + self._start_worker( + WorkspaceWorker.OPERATION_COPY, + src=src_path, + dst=dst_path, + ) + + def _download_item(self, item): + path = item.data(0, Qt.ItemDataRole.UserRole) + name = item.text(0) + is_dir = item.data(0, Qt.ItemDataRole.UserRole + 1) + + if is_dir: + local_path, _ = QFileDialog.getSaveFileName( + self, "保存文件夹", f"{name}.zip", "ZIP 文件 (*.zip)" + ) + if not local_path: + return + self._download_folder_as_zip(path, local_path) + else: + local_path, _ = QFileDialog.getSaveFileName( + self, "保存文件", name, "所有文件 (*)" + ) + if not local_path: + return + self._download_single_file(path, local_path) + + def _cleanup_download_worker(self): + """清理下载线程和 worker""" + if self._download_worker_thread: + try: + self._download_worker_thread.finished.disconnect() + except TypeError, RuntimeError: + pass + if self._download_worker_thread.isRunning(): + self._download_worker_thread.quit() + if not self._download_worker_thread.wait(5000): + self._download_worker_thread.terminate() + self._download_worker_thread.wait(3000) + self._download_worker_thread = None + if self._download_worker: + try: + self._download_worker.log_signal.disconnect() + self._download_worker.finished_signal.disconnect() + self._download_worker.error_signal.disconnect() + except TypeError, RuntimeError: + pass + self._download_worker = None + + def _download_single_file(self, remote_path, local_path): + if not self._config_panel or not self._validate_ssh_config(): + return + + # 确保本地目标目录存在 + local_dir = os.path.dirname(local_path) + if local_dir: + os.makedirs(local_dir, exist_ok=True) + + self._cleanup_download_worker() + + config = self._config_panel.get_config() + self._download_worker = DownloadWorker(config, remote_path, local_path) + self._download_worker_thread = QThread(self) + self._download_worker.moveToThread(self._download_worker_thread) + + self._download_worker.log_signal.connect(self.log_panel.append_log) + self._download_worker.finished_signal.connect(self._on_download_worker_finished) + self._download_worker.error_signal.connect(self._on_download_worker_error) + self._download_worker_thread.started.connect(self._download_worker.run) + self._download_worker_thread.finished.connect(self._cleanup_download_worker) + + self.log_panel.append_log(f"📥 正在下载: {remote_path} -> {local_path}", "info") + self._download_worker_thread.start() + + def _on_download_worker_finished(self, success: bool): + self.log_panel.append_log( + "✅ 下载完成" if success else "❌ 下载失败", + "success" if success else "error", + ) + # 通知线程退出事件循环,触发 finished 信号从而调用 _cleanup_download_worker + if self._download_worker_thread and self._download_worker_thread.isRunning(): + self._download_worker_thread.quit() + + def _on_download_worker_error(self, error_msg: str): + self.log_panel.append_log(f"❌ 下载失败: {error_msg}", "error") + if self._download_worker_thread and self._download_worker_thread.isRunning(): + self._download_worker_thread.quit() + + def _download_folder_as_zip(self, folder_path: str, local_zip_path: str): + if not self._config_panel or not self._validate_ssh_config(): + return + + config = self._config_panel.get_config() + ssh_only = config.get("ssh_only", False) + + try: + if ssh_only: + from virtualbox_client.utils.ssh_manager import SSHManager + + vm = SSHManager( + config["ssh_host"], + config["ssh_port"], + config["ssh_user"], + config["ssh_pass"], + ) + else: + from virtualbox_client.utils.vbox_ssh_manager import VBoxSSHManager + + vm = VBoxSSHManager( + config["vm_name"], + config["ssh_host"], + config["ssh_port"], + config["ssh_user"], + config["ssh_pass"], + ) + + self.log_panel.append_log( + "📦 正在打包远程文件夹(排除 .venv 和 node_modules)...", "info" + ) + zip_name = f"/tmp/workspace_download_{int(time.time())}.zip" + vm.exec_command( + f'cd "{folder_path}" && zip -r "{zip_name}" . -x ".venv/*" "./src/chongming-web/node_modules/*"' + ) + + self.log_panel.append_log("📥 正在下载打包文件...", "info") + success = vm.download_file(zip_name, local_zip_path) + vm.exec_command(f'rm -f "{zip_name}"') + + if success: + self.log_panel.append_log( + f"✅ 文件夹已下载到: {local_zip_path}", "success" + ) + else: + self.log_panel.append_log("❌ 文件夹下载失败", "error") + + except Exception as e: + self.log_panel.append_log(f"❌ 下载失败: {e}", "error") + + def _delete_selected(self): + paths = self._get_selected_paths() + names = self._get_selected_names() + if not paths: + return + + confirm_msg = "确定要删除以下项目吗?\n" + "\n".join(f" - {n}" for n in names) + reply = QMessageBox.question( + self, + "确认删除", + confirm_msg, + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + QMessageBox.StandardButton.No, + ) + if reply != QMessageBox.StandardButton.Yes: + return + + self._delete_paths_serial(paths) + + def _delete_paths_serial(self, paths: list): + """批量删除所有选中路径(单条命令一次性删除所有,避免递归线程)""" + if not paths: + self._soft_refresh() + return + + self._cleanup_worker() + self._set_busy(True, "正在删除...") + + config = self._config_panel.get_config() + self._worker = WorkspaceWorker( + config, WorkspaceWorker.OPERATION_DELETE, path=paths + ) + self._worker_thread = QThread(self) + self._worker.moveToThread(self._worker_thread) + + self._worker.log_signal.connect(self.log_panel.append_log) + self._worker.finished_signal.connect(self._on_batch_delete_finished) + self._worker.error_signal.connect(self._on_worker_error) + self._worker_thread.started.connect(self._worker.run) + self._worker_thread.finished.connect(self._cleanup_worker) + + self._worker_thread.start() + + def _on_batch_delete_finished(self, result): + self._set_busy(False, "就绪") + if result: + self.log_panel.append_log("✅ 删除完成", "success") + else: + self.log_panel.append_log("❌ 删除失败", "error") + self._soft_refresh() + + def _upload_folder_to_workspace(self): + if not self._config_panel or not self._validate_ssh_config(): + return + + local_dir = QFileDialog.getExistingDirectory(self, "选择要上传到工作区的文件夹") + if not local_dir: + return + + folder_name = os.path.basename(local_dir.rstrip("/\\")) + remote_path = f"~/workspace/{folder_name}" + self._start_worker( + WorkspaceWorker.OPERATION_UPLOAD_DIR, + local_path=local_dir, + remote_path=remote_path, + ) + + def _upload_to_workspace(self): + if not self._config_panel or not self._validate_ssh_config(): + return + + local_paths, _ = QFileDialog.getOpenFileNames(self, "选择要上传到工作区的文件") + if not local_paths: + return + + # 只上传第一个文件(后续通过多次点击上传更多文件) + local_path = local_paths[0] + name = os.path.basename(local_path) + remote_path = f"~/workspace/{name}" + self._start_worker( + WorkspaceWorker.OPERATION_UPLOAD, + local_path=local_path, + remote_path=remote_path, + ) + + def _create_folder(self): + name, ok = QInputDialog.getText(self, "新建文件夹", "请输入文件夹名称:") + if not ok or not name.strip(): + return + self._start_worker(WorkspaceWorker.OPERATION_MKDIR, name=name.strip()) + + +class LogPanel(QWidget): + """高性能日志面板(使用 QPlainTextEdit 替代 QTextEdit)""" + + def __init__(self): + super().__init__() + self.setup_ui() + + def setup_ui(self): + layout = QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + + tool_layout = QHBoxLayout() + self.clear_btn = QPushButton("🗑️ 清空") + self.clear_btn.clicked.connect(self.clear_log) + tool_layout.addWidget(self.clear_btn) + tool_layout.addStretch() + self.auto_scroll_check = QCheckBox("📌 自动滚动") + self.auto_scroll_check.setChecked(True) + tool_layout.addWidget(self.auto_scroll_check) + layout.addLayout(tool_layout) + + self.log_output = QPlainTextEdit() + self.log_output.setReadOnly(True) + self.log_output.setMaximumBlockCount(10000) + self.log_output.setFont(QFont("Consolas", 10)) + self.log_output.setStyleSheet(""" + QPlainTextEdit { + background-color: #1e1e1e; + color: #d4d4d4; + border: 1px solid #333; + border-radius: 4px; + padding: 8px; + } + """) + layout.addWidget(self.log_output) + + self._prefix_map = { + "error": "❌ ", + "warning": "⚠️ ", + "success": "✅ ", + "task": "📋 ", + "info": "", + } + + def append_log(self, message: str, level: str = "info"): + timestamp = datetime.now().strftime("%H:%M:%S") + level = self._detect_level(message, level) + prefix = self._prefix_map.get(level, "") + line = f"[{timestamp}] {prefix}{message}" + self.log_output.appendPlainText(line) + if self.auto_scroll_check.isChecked(): + self.log_output.moveCursor(QTextCursor.MoveOperation.End) + + @staticmethod + def _detect_level(message: str, given_level: str) -> str: + if given_level != "info": + return given_level + msg_upper = message.upper() + if any( + x in msg_upper + for x in ["❌", "ERROR", "EXCEPTION", "FAILED", "失败", "错误"] + ): + return "error" + if any(x in msg_upper for x in ["⚠️", "WARN", "跳过", "重试"]): + return "warning" + if any(x in msg_upper for x in ["✅", "SUCCESS", "完成", "成功"]): + return "success" + return "info" + + def clear_log(self): + self.log_output.clear() + + def clear_and_set_header(self, task: str): + self.clear_log() + self.append_log("=" * 60, "info") + self.append_log(f"📋 开始执行任务: {task}", "info") + self.append_log("=" * 60, "info") + + +class TaskPanel(QWidget): + """任务输入和执行面板""" + + def __init__(self, log_panel: LogPanel): + super().__init__() + self.log_panel = log_panel + self._config_panel = None + self._worker_thread = None + self._worker = None + self._shutdown_thread = None + self._shutdown_worker = None + self.setup_ui() + + def set_config_panel(self, panel: ConfigPanel): + self._config_panel = panel + if hasattr(self, "workspace_panel") and self.workspace_panel: + self.workspace_panel._config_panel = panel + + def setup_ui(self): + layout = QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + + self.tab_widget = QTabWidget() + self.tab_widget.setStyleSheet(""" + QTabWidget::pane { border: 1px solid #3c3c3c; border-radius: 4px; background: transparent; } + QTabBar::tab { background: #2d2d2d; color: #ccc; padding: 8px 16px; border: 1px solid #3c3c3c; border-bottom: none; border-top-left-radius: 4px; border-top-right-radius: 4px; } + QTabBar::tab:selected { background: #3c3c3c; color: #569cd6; font-weight: bold; } + QTabBar::tab:hover { background: #4c4c4c; } + """) + + # ====== 任务描述 Tab ====== + task_tab = QWidget() + task_layout = QVBoxLayout(task_tab) + task_layout.setContentsMargins(8, 8, 8, 8) + + input_group = QGroupBox("任务描述") + input_layout = QVBoxLayout(input_group) + + self.task_input = QPlainTextEdit() + self.task_input.setPlaceholderText( + "请输入自然语言任务描述,例如:\n" + " - 在虚拟机中安装 nginx 并确认服务状态\n" + " - 创建一个 /home/test.txt 文件,写入 'Hello World'\n" + " - 查看系统当前的内存使用情况" + ) + self.task_input.setMaximumHeight(100) + self.task_input.setFont(QFont("Microsoft YaHei UI", 10)) + input_layout.addWidget(self.task_input) + + input_layout.addWidget(QLabel("项目路径:")) + self.project_path_edit = QLineEdit() + self.project_path_edit.setPlaceholderText("例如: /home/user/myproject") + input_layout.addWidget(self.project_path_edit) + + task_layout.addWidget(input_group) + + task_btn_layout = QHBoxLayout() + self.run_btn = QPushButton("▶️ 执行任务") + self.run_btn.setStyleSheet(""" + QPushButton { + background-color: #0e639c; color: white; padding: 10px 30px; + font-size: 14px; font-weight: bold; border: none; border-radius: 6px; + } + QPushButton:hover { background-color: #1177bb; } + QPushButton:disabled { background-color: #555; color: #999; } + """) + self.run_btn.clicked.connect(self.run_task) + task_btn_layout.addWidget(self.run_btn) + + self.cancel_btn = QPushButton("⏹️ 取消") + self.cancel_btn.setStyleSheet(""" + QPushButton { + background-color: #d32f2f; color: white; padding: 10px 20px; + font-size: 14px; font-weight: bold; border: none; border-radius: 6px; + } + QPushButton:hover { background-color: #e53935; } + QPushButton:disabled { background-color: #555; color: #999; } + """) + self.cancel_btn.clicked.connect(self.cancel_task) + self.cancel_btn.setEnabled(False) + task_btn_layout.addWidget(self.cancel_btn) + task_layout.addLayout(task_btn_layout) + self.tab_widget.addTab(task_tab, "📋 任务执行") + + # ====== 文件传输 Tab ====== + file_tab = QWidget() + file_layout = QVBoxLayout(file_tab) + file_layout.setContentsMargins(8, 8, 8, 8) + + upload_group = QGroupBox("📤 上传文件到虚拟机") + upload_layout = QFormLayout(upload_group) + + upload_local_layout = QHBoxLayout() + self.upload_local_edit = QLineEdit(placeholderText="选择本地文件路径...") + upload_local_layout.addWidget(self.upload_local_edit) + self.upload_browse_btn = QPushButton("📂 浏览") + self.upload_browse_btn.setFixedWidth(80) + self.upload_browse_btn.clicked.connect(self._browse_upload_file) + upload_local_layout.addWidget(self.upload_browse_btn) + upload_layout.addRow("本地文件:", upload_local_layout) + + self.upload_remote_edit = QLineEdit( + placeholderText="例如: ~/workspace/target_file.txt" + ) + self.upload_remote_edit.setText("~/workspace/") + upload_layout.addRow("远程路径:", self.upload_remote_edit) + + self.upload_btn = QPushButton("📤 上传") + self.upload_btn.setStyleSheet(""" + QPushButton { background-color: #2d7d46; color: white; padding: 8px 20px; + font-size: 13px; font-weight: bold; border: none; border-radius: 4px; } + QPushButton:hover { background-color: #389e54; } + QPushButton:disabled { background-color: #555; color: #999; } + """) + self.upload_btn.clicked.connect(self.upload_file) + upload_layout.addRow("", self.upload_btn) + file_layout.addWidget(upload_group) + + download_group = QGroupBox("📥 从虚拟机下载文件") + download_layout = QFormLayout(download_group) + + self.download_remote_edit = QLineEdit( + placeholderText="例如: ~/workspace/source_file.txt" + ) + download_layout.addRow("远程路径:", self.download_remote_edit) + + download_local_layout = QHBoxLayout() + self.download_local_edit = QLineEdit(placeholderText="选择本地保存路径...") + download_local_layout.addWidget(self.download_local_edit) + self.download_browse_btn = QPushButton("📂 浏览") + self.download_browse_btn.setFixedWidth(80) + self.download_browse_btn.clicked.connect(self._browse_download_file) + download_local_layout.addWidget(self.download_browse_btn) + download_layout.addRow("保存到:", download_local_layout) + + self.download_btn = QPushButton("📥 下载") + self.download_btn.setStyleSheet(""" + QPushButton { background-color: #8b5cf6; color: white; padding: 8px 20px; + font-size: 13px; font-weight: bold; border: none; border-radius: 4px; } + QPushButton:hover { background-color: #a78bfa; } + QPushButton:disabled { background-color: #555; color: #999; } + """) + self.download_btn.clicked.connect(self.download_file) + download_layout.addRow("", self.download_btn) + file_layout.addWidget(download_group) + + file_layout.addStretch() + self.tab_widget.addTab(file_tab, "📁 文件传输") + + # ====== Script Execution Tab ====== + script_tab = QWidget() + script_layout = QVBoxLayout(script_tab) + script_layout.setContentsMargins(8, 8, 8, 8) + + script_group = QGroupBox("Upload and Execute Script") + script_group_layout = QVBoxLayout(script_group) + + script_path_layout = QHBoxLayout() + self.script_path_edit = QLineEdit( + placeholderText="Select local .sh script file..." + ) + script_path_layout.addWidget(self.script_path_edit) + self.script_browse_btn = QPushButton("Browse") + self.script_browse_btn.setFixedWidth(80) + self.script_browse_btn.clicked.connect(self._browse_script) + script_path_layout.addWidget(self.script_browse_btn) + script_group_layout.addWidget(QLabel("Local Script:")) + script_group_layout.addLayout(script_path_layout) + + self.run_script_btn = QPushButton("Upload and Execute Script") + self.run_script_btn.setStyleSheet(""" + QPushButton { background-color: #8e44ad; color: white; padding: 10px 30px; + font-size: 14px; font-weight: bold; border: none; border-radius: 6px; } + QPushButton:hover { background-color: #9b59b6; } + QPushButton:disabled { background-color: #555; color: #999; } + """) + self.run_script_btn.clicked.connect(self._run_script) + script_group_layout.addWidget(self.run_script_btn) + + script_layout.addWidget(script_group) + + desc_group = QGroupBox("How it works") + desc_layout = QVBoxLayout(desc_group) + desc_label = QLabel( + "1. Select a local .sh script file\n" + "2. Click button to auto:\n" + " Upload script to /tmp/\n" + " Execute (deploy_* scripts use sudo, others run as normal user)\n" + " Download execution log\n" + " Clean remote temp files" + ) + desc_label.setStyleSheet("color: #aaa; font-size: 12px; line-height: 1.6;") + desc_layout.addWidget(desc_label) + script_layout.addWidget(desc_group) + + script_layout.addStretch() + self.tab_widget.addTab(script_tab, "Script") + + # ====== 工作区 Tab ====== + self.workspace_panel = WorkspacePanel( + self._config_panel or ConfigPanel(), self.log_panel + ) + self.tab_widget.addTab(self.workspace_panel, "📂 工作区") + + self.tab_widget.currentChanged.connect(self._on_tab_changed) + + layout.addWidget(self.tab_widget) + + def _on_tab_changed(self, index: int): + if self.tab_widget.tabText(index) == "📂 工作区": + if self._config_panel and self.workspace_panel._validate_ssh_config(): + QTimer.singleShot(300, self.workspace_panel.refresh_root) + + def _set_ui_running(self, running: bool): + self.run_btn.setEnabled(not running) + self.cancel_btn.setEnabled(running) + self.task_input.setEnabled(not running) + self.run_script_btn.setEnabled(not running) + self.script_browse_btn.setEnabled(not running) + self.script_path_edit.setEnabled(not running) + if self._config_panel: + self._config_panel.set_enabled(not running) + + def _cleanup_worker(self): + if self._worker_thread: + try: + self._worker_thread.finished.disconnect() + except TypeError, RuntimeError: + pass + if self._worker_thread.isRunning(): + self._worker_thread.quit() + if not self._worker_thread.wait(5000): + self._worker_thread.terminate() + self._worker_thread.wait(3000) + self._worker_thread = None + if self._worker: + try: + self._worker.log_signal.disconnect() + self._worker.finished_signal.disconnect() + self._worker.error_signal.disconnect() + self._worker.cancelled_signal.disconnect() + except TypeError, RuntimeError: + pass + self._worker = None + + def _ensure_old_threads_cleaned(self): + self._cleanup_worker() + self._cleanup_shutdown() + + def run_task(self): + if not self._config_panel or not self._config_panel.validate(): + return + task = self.task_input.toPlainText().strip() + if not task: + QMessageBox.warning(self, "提示", "请输入任务描述") + return + + project_path = self.project_path_edit.text().strip() + if not project_path: + QMessageBox.warning(self, "提示", "请输入项目路径") + return + + project_name = os.path.basename(project_path.rstrip("/\\")) + enhanced_task = ( + f'在 {project_path} 下有一个 "{project_name}" 项目,' + f"请仔细阅读 README.md 文档再执行操作," + f"最后将更新记录写入 README.md 中," + f"不允许使用启动或打包项目\n\n{task}" + ) + + config = self._config_panel.get_config() + self._ensure_old_threads_cleaned() + self._set_ui_running(True) + self.log_panel.clear_and_set_header(enhanced_task) + + self._worker = AgentWorker(config, enhanced_task) + self._worker_thread = QThread(self) + self._worker.moveToThread(self._worker_thread) + + self._worker.log_signal.connect(self.log_panel.append_log) + self._worker.finished_signal.connect(self._on_task_finished) + self._worker.error_signal.connect(self._on_task_error) + self._worker.cancelled_signal.connect(self._on_task_cancelled) + self._worker_thread.started.connect(self._worker.run) + self._worker_thread.finished.connect(self._on_worker_thread_finished) + + self._worker_thread.start() + + def _on_worker_thread_finished(self): + self._cleanup_worker() + + def _on_task_finished(self, vm_started_by_us: bool): + self._set_ui_running(False) + self.log_panel.append_log("=" * 60, "info") + self.log_panel.append_log("✅ 任务已成功完成", "success") + + if vm_started_by_us: + reply = QMessageBox.question( + self, + "虚拟机管理", + "虚拟机是由本任务启动的。\n是否要关闭虚拟机?\n\n" + "选「是」→ 关闭虚拟机\n" + "选「否」→ 保持虚拟机运行", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + QMessageBox.StandardButton.Yes, + ) + if reply == QMessageBox.StandardButton.Yes: + config = self._config_panel.get_config() if self._config_panel else {} + self._start_shutdown_thread(config) + + def _on_task_error(self, error_msg: str): + self._set_ui_running(False) + self.log_panel.append_log(f"❌ 任务失败: {error_msg}", "error") + + def _on_task_cancelled(self): + self._set_ui_running(False) + self.log_panel.append_log("⏹️ 任务已取消", "warning") + + def _start_shutdown_thread(self, config: dict): + self._cleanup_shutdown() + self._shutdown_worker = ShutdownWorker(config) + self._shutdown_thread = QThread(self) + self._shutdown_worker.moveToThread(self._shutdown_thread) + + self._shutdown_worker.log.connect(self.log_panel.append_log) + self._shutdown_worker.finished.connect(self._on_shutdown_finished) + self._shutdown_worker.error.connect(self._on_shutdown_error) + self._shutdown_thread.started.connect(self._shutdown_worker.run) + self._shutdown_thread.finished.connect(self._cleanup_shutdown) + + self._shutdown_thread.start() + + def _on_shutdown_finished(self): + self.log_panel.append_log("✅ 虚拟机已关闭", "success") + + def _on_shutdown_error(self, error_msg: str): + self.log_panel.append_log(f"❌ 关机失败: {error_msg}", "error") + + def _cleanup_shutdown(self): + if self._shutdown_thread: + try: + self._shutdown_thread.finished.disconnect() + except TypeError, RuntimeError: + pass + if self._shutdown_thread.isRunning(): + self._shutdown_thread.quit() + if not self._shutdown_thread.wait(5000): + self._shutdown_thread.terminate() + self._shutdown_thread.wait(3000) + self._shutdown_thread = None + if self._shutdown_worker: + try: + self._shutdown_worker.log.disconnect() + self._shutdown_worker.finished.disconnect() + self._shutdown_worker.error.disconnect() + except TypeError, RuntimeError: + pass + self._shutdown_worker = None + + def cancel_task(self): + if self._worker: + self._worker.cancel() + self.log_panel.append_log("⏹️ 正在取消任务...", "warning") + QTimer.singleShot(5000, self._force_cleanup_if_stuck) + + def _force_cleanup_if_stuck(self): + if self._worker_thread and self._worker_thread.isRunning(): + self.log_panel.append_log("⚠️ 任务线程未响应,正在强制终止...", "warning") + self._worker_thread.quit() + if not self._worker_thread.wait(5000): + self._worker_thread.terminate() + self._worker_thread.wait(3000) + self._cleanup_worker() + self._set_ui_running(False) + self.log_panel.append_log("⏹️ 任务已强制终止", "warning") + + _upload_thread = None + _upload_worker = None + + def _cleanup_upload(self): + if self._upload_thread: + try: + self._upload_thread.finished.disconnect() + except TypeError, RuntimeError: + pass + if self._upload_thread.isRunning(): + self._upload_thread.quit() + if not self._upload_thread.wait(5000): + self._upload_thread.terminate() + self._upload_thread.wait(3000) + self._upload_thread = None + if self._upload_worker: + try: + self._upload_worker.log_signal.disconnect() + self._upload_worker.finished_signal.disconnect() + self._upload_worker.error_signal.disconnect() + except TypeError, RuntimeError: + pass + self._upload_worker = None + + def _browse_upload_file(self): + file_path, _ = QFileDialog.getOpenFileName(self, "选择要上传的文件") + if file_path: + self.upload_local_edit.setText(file_path) + name = os.path.basename(file_path) + remote = self.upload_remote_edit.text().strip() + if remote.endswith("/"): + self.upload_remote_edit.setText(remote + name) + else: + self.upload_remote_edit.setText(f"~/workspace/{name}") + + def upload_file(self): + if not self._config_panel or not self._config_panel.validate(): + return + local_path = self.upload_local_edit.text().strip() + remote_path = self.upload_remote_edit.text().strip() + if not local_path: + QMessageBox.warning(self, "提示", "请选择要上传的本地文件") + return + if not remote_path: + QMessageBox.warning(self, "提示", "请输入虚拟机中的目标路径") + return + if not os.path.isfile(local_path): + QMessageBox.warning(self, "提示", f"本地文件不存在: {local_path}") + return + + self._cleanup_upload() + self._set_ui_running(True) + + config = self._config_panel.get_config() + self._upload_worker = UploadWorker(config, local_path, remote_path) + self._upload_thread = QThread(self) + self._upload_worker.moveToThread(self._upload_thread) + + self._upload_worker.log_signal.connect(self.log_panel.append_log) + self._upload_worker.finished_signal.connect(self._on_upload_finished) + self._upload_worker.error_signal.connect(self._on_upload_error) + self._upload_thread.started.connect(self._upload_worker.run) + self._upload_thread.finished.connect(self._cleanup_upload) + + self._upload_thread.start() + + def _on_upload_finished(self, success: bool): + self._set_ui_running(False) + if success: + self.log_panel.append_log("✅ 上传操作已完成", "success") + + def _on_upload_error(self, error_msg: str): + self._set_ui_running(False) + self.log_panel.append_log(f"❌ 上传失败: {error_msg}", "error") + + _download_thread = None + _download_worker = None + + def _cleanup_download(self): + if self._download_thread: + try: + self._download_thread.finished.disconnect() + except TypeError, RuntimeError: + pass + if self._download_thread.isRunning(): + self._download_thread.quit() + if not self._download_thread.wait(5000): + self._download_thread.terminate() + self._download_thread.wait(3000) + self._download_thread = None + if self._download_worker: + try: + self._download_worker.log_signal.disconnect() + self._download_worker.finished_signal.disconnect() + self._download_worker.error_signal.disconnect() + except TypeError, RuntimeError: + pass + self._download_worker = None + + def _browse_download_file(self): + file_path, _ = QFileDialog.getSaveFileName(self, "选择保存位置") + if file_path: + self.download_local_edit.setText(file_path) + + def download_file(self): + if not self._config_panel or not self._config_panel.validate(): + return + remote_path = self.download_remote_edit.text().strip() + local_path = self.download_local_edit.text().strip() + if not remote_path: + QMessageBox.warning(self, "提示", "请输入虚拟机中的文件路径") + return + if not local_path: + QMessageBox.warning(self, "提示", "请选择本地保存路径") + return + + self._cleanup_download() + self._set_ui_running(True) + + config = self._config_panel.get_config() + self._download_worker = DownloadWorker(config, remote_path, local_path) + self._download_thread = QThread(self) + self._download_worker.moveToThread(self._download_thread) + + self._download_worker.log_signal.connect(self.log_panel.append_log) + self._download_worker.finished_signal.connect(self._on_download_finished) + self._download_worker.error_signal.connect(self._on_download_error) + self._download_thread.started.connect(self._download_worker.run) + self._download_thread.finished.connect(self._cleanup_download) + + self._download_thread.start() + + def _on_download_finished(self, success: bool): + self._set_ui_running(False) + if success: + self.log_panel.append_log("✅ 下载操作已完成", "success") + + def _on_download_error(self, error_msg: str): + self._set_ui_running(False) + self.log_panel.append_log(f"❌ 下载失败: {error_msg}", "error") + + _script_thread = None + _script_worker = None + + def _cleanup_script(self): + if self._script_thread: + try: + self._script_thread.finished.disconnect() + except TypeError, RuntimeError: + pass + if self._script_thread.isRunning(): + self._script_thread.quit() + if not self._script_thread.wait(5000): + self._script_thread.terminate() + self._script_thread.wait(3000) + self._script_thread = None + if self._script_worker: + try: + self._script_worker.log_signal.disconnect() + self._script_worker.finished_signal.disconnect() + self._script_worker.error_signal.disconnect() + except TypeError, RuntimeError: + pass + self._script_worker = None + + def _browse_script(self): + file_path, _ = QFileDialog.getOpenFileName( + self, "Select script", "", "Shell scripts (*.sh);;All files (*)" + ) + if file_path: + self.script_path_edit.setText(file_path) + + def _run_script(self): + if not self._config_panel or not self._config_panel.validate(): + return + local_path = self.script_path_edit.text().strip() + if not local_path: + QMessageBox.warning(self, "Hint", "Please select a script file") + return + if not os.path.isfile(local_path): + QMessageBox.warning(self, "Hint", "File not found: " + local_path) + return + + self._cleanup_script() + self._set_ui_running(True) + self.tab_widget.setCurrentIndex(0) + self.log_panel.clear_log() + + config = self._config_panel.get_config() + self._script_worker = ScriptWorker(config, local_path) + self._script_thread = QThread(self) + self._script_worker.moveToThread(self._script_thread) + + self._script_worker.log_signal.connect(self.log_panel.append_log) + self._script_worker.finished_signal.connect(self._on_script_finished) + self._script_worker.error_signal.connect(self._on_script_error) + self._script_thread.started.connect(self._script_worker.run) + self._script_thread.finished.connect(self._cleanup_script) + + self._script_thread.start() + + def _on_script_finished(self, success: bool): + self._set_ui_running(False) + if success: + self.log_panel.append_log("Script execution completed", "success") + + def _on_script_error(self, error_msg: str): + self._set_ui_running(False) + self.log_panel.append_log("Script error: " + error_msg, "error") + + +class MainWindow(QMainWindow): + def __init__(self): + super().__init__() + self.setWindowTitle("VirtualBox Client - 虚拟机管理助手") + self.setMinimumSize(1200, 800) + self.setup_ui() + self._center_window() + + def _center_window(self): + screen = QApplication.primaryScreen().geometry() + self.move( + (screen.width() - self.width()) // 2, (screen.height() - self.height()) // 2 + ) + + def setup_ui(self): + central = QWidget() + self.setCentralWidget(central) + main_layout = QHBoxLayout(central) + main_layout.setContentsMargins(10, 10, 10, 10) + + splitter = QSplitter(Qt.Orientation.Horizontal) + + left = QWidget() + left_layout = QVBoxLayout(left) + left_layout.setContentsMargins(0, 0, 5, 0) + scroll = QScrollArea() + scroll.setWidgetResizable(True) + scroll.setFrameShape(QFrame.Shape.NoFrame) + self.config_panel = ConfigPanel() + scroll.setWidget(self.config_panel) + left_layout.addWidget(scroll) + left.setMinimumWidth(320) + left.setMaximumWidth(400) + splitter.addWidget(left) + + right = QWidget() + right_layout = QVBoxLayout(right) + right_layout.setContentsMargins(5, 0, 0, 0) + self.log_panel = LogPanel() + right_layout.addWidget(self.log_panel, stretch=2) + self.task_panel = TaskPanel(self.log_panel) + self.task_panel.set_config_panel(self.config_panel) + right_layout.addWidget(self.task_panel, stretch=1) + splitter.addWidget(right) + + splitter.setSizes([350, 850]) + main_layout.addWidget(splitter) + + self.status_bar = QStatusBar() + self.setStatusBar(self.status_bar) + self.status_label = QLabel("就绪 - 请配置虚拟机连接信息并输入任务") + self.status_bar.addWidget(self.status_label, 1) + + self.setStyleSheet(""" + QMainWindow { background-color: #252526; } + QWidget { color: #cccccc; font-size: 13px; } + QGroupBox { + font-weight: bold; border: 1px solid #3c3c3c; + border-radius: 6px; margin-top: 12px; padding-top: 16px; + } + QGroupBox::title { + subcontrol-origin: margin; left: 10px; padding: 0 5px; color: #569cd6; + } + QLineEdit, QSpinBox { + background-color: #3c3c3c; color: #d4d4d4; + border: 1px solid #555; border-radius: 3px; padding: 5px; + } + QLineEdit:focus, QSpinBox:focus { border-color: #0e639c; } + QPushButton { + background-color: #3c3c3c; color: #cccccc; + border: 1px solid #555; border-radius: 4px; padding: 6px 12px; + } + QPushButton:hover { background-color: #4c4c4c; border-color: #0e639c; } + QScrollArea { border: none; background-color: transparent; } + QPlainTextEdit { background-color: #1e1e1e; border: 1px solid #3c3c3c; border-radius: 4px; } + QSplitter::handle { background-color: #3c3c3c; width: 2px; } + """) + + +def run(): + app = QApplication(sys.argv) + app.setApplicationName("VirtualBox Client") + window = MainWindow() + window.show() + sys.exit(app.exec()) + + +if __name__ == "__main__": + run() diff --git a/src/virtualbox_client/scripts/__init__.py b/src/virtualbox_client/scripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/virtualbox_client/scripts/agent_task.py b/src/virtualbox_client/scripts/agent_task.py new file mode 100644 index 0000000..06acd47 --- /dev/null +++ b/src/virtualbox_client/scripts/agent_task.py @@ -0,0 +1,59 @@ +import os +import sys + +# 尝试加载 .env 文件(如果存在) +try: + from dotenv import load_dotenv + + load_dotenv("./.env.example") +except ImportError: + pass + +from virtualbox_client.utils.vbox_ssh_manager import VBoxSSHManager +from virtualbox_client.utils.vm_agent import VMAgent + + +def run(): + # 从环境变量读取配置 + vm_name = os.getenv("VM_NAME", "ubuntu20.04") + ssh_host = os.getenv("VM_SSH_HOST", "127.0.0.1") + ssh_port = int(os.getenv("VM_SSH_PORT", "2222")) + ssh_user = os.getenv("VM_SSH_USER") + ssh_password = os.getenv("VM_SSH_PASS") + api_key = os.getenv("LLM_API_KEY") + base_url = os.getenv("LLM_BASE_URL") # 可选,用于本地模型 + model = os.getenv("LLM_MODEL", "gpt-4o") + + # 参数校验 + if not ssh_user or not ssh_password: + print("❌ 请设置环境变量 VM_SSH_USER 和 VM_SSH_PASS") + sys.exit(1) + if not api_key: + print("❌ 请设置环境变量 LLM_API_KEY") + sys.exit(1) + + if len(sys.argv) < 2: + print("用法: uv run agent-task <任务描述>") + print("示例: uv run agent-task '在虚拟机中安装 nginx 并确认服务状态'") + sys.exit(1) + + task = " ".join(sys.argv[1:]) + + # 创建管理器和 Agent + vm = VBoxSSHManager( + vm_name, + ssh_host, + ssh_port, + ssh_user, + ssh_password, + ) + agent = VMAgent( + vm, + api_key=api_key, + base_url=base_url, + model=model, + sudo_password=os.getenv("VM_SSH_PASS"), + ) + + final_msg = agent.run(task) + print(final_msg) diff --git a/src/virtualbox_client/scripts/example.py b/src/virtualbox_client/scripts/example.py new file mode 100644 index 0000000..7634acc --- /dev/null +++ b/src/virtualbox_client/scripts/example.py @@ -0,0 +1,49 @@ +import sys +import os +from ..utils.vbox_ssh_manager import VBoxSSHManager + + +# ---------- 使用示例 ---------- +def run(): + # 配置参数 + VM_NAME = "ubuntu20.04" + SSH_HOST = "127.0.0.1" # 根据网络配置修改 + SSH_PORT = 2222 # 默认 SSH 端口或转发端口 + SSH_USER = "chakcy" + SSH_PASS = "020709djh" + + # 创建管理工具实例 + vm = VBoxSSHManager(VM_NAME, SSH_HOST, SSH_PORT, SSH_USER, SSH_PASS) + + try: + # 启动虚拟机并等待 SSH 就绪 + if not vm.start_vm(): + sys.exit(1) + + # 1. 上传测试文件 + print("\n--- 上传文件测试 ---") + local_test_file = "test_upload.txt" + with open(local_test_file, "w") as f: + f.write("Hello from host!") + vm.upload_file(local_test_file, "/home/chakcy/test_upload.txt") + os.remove(local_test_file) + + # 2. 执行命令 + print("\n--- 执行命令测试 ---") + out, err, code = vm.exec_command("ls -l /home/chakcy && echo '--- done ---'") + if out: + print(out) + if err: + print("[STDERR]", err) + print(f"退出码: {code}") + + # 3. 下载文件 + print("\n--- 下载文件测试 ---") + vm.download_file("/home/chakcy/test_upload.txt", "downloaded.txt") + if os.path.exists("downloaded.txt"): + with open("downloaded.txt") as f: + print("下载内容:", f.read()) + + finally: + # 关闭虚拟机并清理资源 + vm.stop_vm() diff --git a/src/virtualbox_client/scripts/run_script.py b/src/virtualbox_client/scripts/run_script.py new file mode 100644 index 0000000..f16a5a6 --- /dev/null +++ b/src/virtualbox_client/scripts/run_script.py @@ -0,0 +1,137 @@ +""" +上传本地 sh 脚本到虚拟机 → 执行脚本 → 下载执行日志 + +用法: + uv run run-script <本地脚本路径> + uv run run-script resources/deploy_docker.sh + uv run run-script resources/chongming_dev.sh + +脚本会将本地 sh 文件上传到 /tmp/ 目录,执行,并将 stdout + stderr +保存到 /tmp/<脚本名>.log,最后下载回本地。 + +权限说明: + - deploy_*.sh → 自动以 sudo 权限执行(系统级安装脚本) + - 其他脚本 → 以普通用户权限执行(如开发环境脚本) +""" + +import sys +import os + +# 尝试加载 .env 文件(如果存在) +try: + from dotenv import load_dotenv + + load_dotenv("./.env.example") +except ImportError: + pass + +from virtualbox_client.utils.vbox_ssh_manager import VBoxSSHManager + + +def run(): + # 从环境变量读取配置 + vm_name = os.getenv("VM_NAME", "ubuntu20.04") + ssh_host = os.getenv("VM_SSH_HOST", "127.0.0.1") + ssh_port = int(os.getenv("VM_SSH_PORT", "2222")) + ssh_user = os.getenv("VM_SSH_USER") + ssh_password = os.getenv("VM_SSH_PASS") + + # 参数校验 + if not ssh_user or not ssh_password: + print("❌ 请设置环境变量 VM_SSH_USER 和 VM_SSH_PASS") + sys.exit(1) + + if len(sys.argv) < 2: + print("用法: uv run run-script <本地脚本路径>") + print("示例: uv run run-script resources/deploy_docker.sh") + sys.exit(1) + + local_path = sys.argv[1] + + # 检查本地脚本是否存在 + if not os.path.isfile(local_path): + print(f"❌ 本地脚本不存在: {local_path}") + sys.exit(1) + + # 获取脚本文件名 + script_name = os.path.basename(local_path) + remote_path = f"/tmp/{script_name}" + log_path = f"/tmp/{script_name}.log" + local_log_path = f"{script_name}.log" + + # 创建管理器和连接 + vm = VBoxSSHManager( + vm_name, + ssh_host, + ssh_port, + ssh_user, + ssh_password, + ) + + try: + # 启动虚拟机 + if not vm.start_vm(): + print("❌ 虚拟机启动失败") + sys.exit(1) + + # 1. 上传脚本 + print(f"\n{'='*50}") + print(f"📤 [1/3] 上传脚本: {local_path} -> {remote_path}") + print(f"{'='*50}") + if not vm.upload_file(local_path, remote_path): + print("❌ 上传失败") + sys.exit(1) + + # 2. 执行脚本 + print(f"\n{'='*50}") + print(f"⚡ [2/3] 执行脚本: {remote_path}") + print(f"{'='*50}") + print(f" 日志将保存到: {log_path}") + print() + + # 赋予执行权限并执行,输出同时写入终端(exec_command 实时打印)和日志文件 + # deploy_* 脚本需要 sudo 权限(系统级安装),其余脚本以普通用户执行 + if script_name.startswith("deploy_"): + cmd = ( + f"chmod +x {remote_path} && " + f"echo '{ssh_password}' | sudo -S {remote_path} 2>&1 | " + f"tee {log_path}" + ) + print(" 🔑 检测到部署脚本,以 sudo 权限执行") + else: + cmd = f"chmod +x {remote_path} && " f"{remote_path} 2>&1 | tee {log_path}" + print(" 👤 检测到开发脚本,以普通用户权限执行") + out, err, code = vm.exec_command(cmd, get_pty=False) + + print(f"\n✔ 脚本执行完成,退出码: {code}") + + # 3. 下载日志 + print(f"\n{'='*50}") + print(f"📥 [3/3] 下载执行日志: {log_path} -> {local_log_path}") + print(f"{'='*50}") + if vm.download_file(log_path, local_log_path): + print(f"✔ 日志已保存到本地: {local_log_path}") + else: + # 如果 tee 方式没拿到日志,尝试直接读取 + print("⚠ 尝试直接下载日志...") + if vm.download_file(log_path, local_log_path): + print(f"✔ 日志已保存到本地: {local_log_path}") + else: + print("⚠ 日志文件未生成,将终端输出保存到本地") + if out: + with open(local_log_path, "w") as f: + f.write(out) + print(f"✔ 终端输出已保存到: {local_log_path}") + + # 清理远程临时文件 + print(f"\n{'='*50}") + print("🧹 清理远程临时文件...") + vm.exec_command(f"rm -f {remote_path} {log_path}") + print("✔ 清理完成") + + print(f"\n{'='*50}") + print("🎉 全部完成!") + print(f"{'='*50}") + + finally: + vm.stop_vm() diff --git a/src/virtualbox_client/utils/__init__.py b/src/virtualbox_client/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/virtualbox_client/utils/ssh_manager.py b/src/virtualbox_client/utils/ssh_manager.py new file mode 100644 index 0000000..039bbe5 --- /dev/null +++ b/src/virtualbox_client/utils/ssh_manager.py @@ -0,0 +1,264 @@ +""" +纯 SSH 连接管理工具(不依赖 VirtualBox/vboxapi) + +提供 SSH 连接、命令执行、文件上传/下载功能。 +与 VBoxSSHManager 保持兼容的接口,但剥离了所有 VirtualBox 依赖。 +""" + +import paramiko +import time +import socket +import threading + + +class SSHManager: + """纯 SSH 管理工具,不依赖 VirtualBox""" + + def __init__( + self, + ssh_host, + ssh_port, + ssh_user, + ssh_password, + ssh_timeout=180, + cmd_timeout=120, + ): + self.ssh_host = ssh_host + self.ssh_port = ssh_port + self.ssh_user = ssh_user + self.ssh_pass = ssh_password + self.ssh_timeout = ssh_timeout + self.cmd_timeout = cmd_timeout + + self._ssh_client = None + self._cancelled = threading.Event() + + def set_cancel_event(self, event: threading.Event): + """设置外部取消事件,与 GUI 层的取消信号同步""" + self._cancelled = event + + def cancel_operations(self): + """标记取消所有正在进行的操作""" + self._cancelled.set() + if self._ssh_client: + try: + self._ssh_client.close() + except Exception: + pass + self._ssh_client = None + + def reset_cancel(self): + """重置取消标记""" + self._cancelled.clear() + + def is_cancelled(self): + """检查是否已取消""" + return self._cancelled.is_set() + + # ---------- SSH 连接管理 ---------- + def get_ssh_client(self): + """获取或创建 SSH 连接(带重试)""" + if self._ssh_client is not None: + return self._ssh_client + + for i in range(5): + if self._cancelled.is_set(): + return None + if not self._is_port_open(self.ssh_host, self.ssh_port): + print(f"SSH 端口 {self.ssh_host}:{self.ssh_port} 不可达,重试 {i+1}/5") + time.sleep(10) + continue + try: + client = paramiko.SSHClient() + client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + client.connect( + self.ssh_host, + port=self.ssh_port, + username=self.ssh_user, + password=self.ssh_pass, + timeout=10, + ) + self._ssh_client = client + return client + except Exception as e: + print(f"SSH 连接失败 ({i+1}/5): {e}") + if self._cancelled.is_set(): + return None + time.sleep(10) + return None + + def close_ssh(self): + """关闭 SSH 连接""" + if self._ssh_client: + self._ssh_client.close() + self._ssh_client = None + + # ---------- 虚拟机生命周期(空实现,兼容接口) ---------- + def is_running(self) -> bool: + """SSH-only 模式下始终返回 True(不管理虚拟机生命周期)""" + return True + + def start_vm(self, wait_for_ssh=True): + """SSH-only 模式下直接等待 SSH 就绪""" + if wait_for_ssh: + return self._wait_for_ssh_ready(self.ssh_timeout) + return True + + def stop_vm(self): + """SSH-only 模式下不执行任何操作""" + self.close_ssh() + + def ensure_vm_stopped(self): + """SSH-only 模式下不执行任何操作""" + return False + + # ---------- 核心功能 ---------- + def exec_command(self, command, get_pty=False): + """ + 执行命令并返回 (stdout_str, stderr_str, exit_code) + 边执行边打印输出,方便实时查看进度 + 如果连接失败返回 (None, None, -1) + """ + client = self.get_ssh_client() + if not client: + return None, None, -1 + try: + stdin, stdout, stderr = client.exec_command( + command, timeout=self.cmd_timeout, get_pty=get_pty + ) + + out_lines = [] + for line in stdout: + if self._cancelled.is_set(): + stdout.channel.close() + break + if isinstance(line, bytes): + decoded = line.decode("utf-8", errors="replace").rstrip("\n") + else: + decoded = str(line).rstrip("\n") + print(f" {decoded}") + out_lines.append(decoded) + + err_lines = [] + for line in stderr: + if self._cancelled.is_set(): + stderr.channel.close() + break + if isinstance(line, bytes): + decoded = line.decode("utf-8", errors="replace").rstrip("\n") + else: + decoded = str(line).rstrip("\n") + print(f" [stderr] {decoded}") + err_lines.append(decoded) + + out = "\n".join(out_lines) + err = "\n".join(err_lines) + exit_code = stdout.channel.recv_exit_status() + return out, err, exit_code + except Exception as e: + if self._cancelled.is_set(): + return None, None, -1 + print(f"执行命令失败: {e}") + return None, None, -1 + + def upload_file(self, local_path, remote_path): + """上传本地文件到远程服务器,返回是否成功""" + client = self.get_ssh_client() + if not client: + return False + try: + sftp = client.open_sftp() + sftp.put(local_path, remote_path) + sftp.close() + print(f"✔ 上传成功: {local_path} -> {remote_path}") + return True + except Exception as e: + if self._cancelled.is_set(): + return False + print(f"✘ 上传失败: {e}") + return False + + def upload_directory(self, local_dir, remote_dir): + """递归上传整个文件夹到远程服务器,返回是否成功""" + import os as _os + + client = self.get_ssh_client() + if not client: + return False + try: + sftp = client.open_sftp() + local_dir = _os.path.normpath(local_dir) + remote_dir = remote_dir.rstrip("/") + # 展开 ~ 为真实路径(SFTP 不会自动展开 ~) + if remote_dir.startswith("~/"): + out, _, _ = self.exec_command("echo ~") + if out and out.strip(): + remote_dir = out.strip() + remote_dir[1:] + # 创建远程根目录 + self.exec_command(f'mkdir -p "{remote_dir}"') + for root, dirs, files in _os.walk(local_dir): + # 计算相对路径 + rel_path = _os.path.relpath(root, local_dir) + if rel_path == ".": + rel_path = "" + # 创建远程子目录 + target_dir = remote_dir + ( + "/" + rel_path.replace("\\", "/") if rel_path else "" + ) + self.exec_command(f'mkdir -p "{target_dir}"') + for fname in files: + if self._cancelled.is_set(): + sftp.close() + return False + local_file = _os.path.join(root, fname) + remote_file = target_dir + "/" + fname + sftp.put(local_file, remote_file) + print(f" ✔ {local_file} -> {remote_file}") + sftp.close() + print(f"✔ 文件夹上传成功: {local_dir} -> {remote_dir}") + return True + except Exception as e: + if self._cancelled.is_set(): + return False + print(f"✘ 文件夹上传失败: {e}") + return False + + def download_file(self, remote_path, local_path): + """从远程服务器下载文件到本地,返回是否成功""" + client = self.get_ssh_client() + if not client: + return False + try: + sftp = client.open_sftp() + sftp.get(remote_path, local_path) + sftp.close() + print(f"✔ 下载成功: {remote_path} -> {local_path}") + return True + except Exception as e: + if self._cancelled.is_set(): + return False + print(f"✘ 下载失败: {e}") + return False + + # ---------- 内部辅助方法 ---------- + def _wait_for_ssh_ready(self, timeout=180): + """等待 SSH 端口可连接""" + start = time.time() + while time.time() - start < timeout: + if self._cancelled.is_set(): + return False + if self._is_port_open(self.ssh_host, self.ssh_port): + return True + time.sleep(2) + return False + + @staticmethod + def _is_port_open(host, port, timeout=5): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(timeout) + try: + s.connect((host, port)) + s.close() + return True + except Exception: + return False diff --git a/src/virtualbox_client/utils/vbox_ssh_manager.py b/src/virtualbox_client/utils/vbox_ssh_manager.py new file mode 100644 index 0000000..2d0dafe --- /dev/null +++ b/src/virtualbox_client/utils/vbox_ssh_manager.py @@ -0,0 +1,385 @@ +import paramiko +import time +import socket +import threading +from vboxapi import VirtualBoxManager + + +class VBoxSSHManager: + """VirtualBox 虚拟机 SSH 管理工具,支持上传、执行、下载文件""" + + def __init__( + self, + vm_name, + ssh_host, + ssh_port, + ssh_user, + ssh_password, + ssh_timeout=180, + cmd_timeout=120, + ): + self.vm_name = vm_name + self.ssh_host = ssh_host + self.ssh_port = ssh_port + self.ssh_user = ssh_user + self.ssh_pass = ssh_password + self.ssh_timeout = ssh_timeout + self.cmd_timeout = cmd_timeout + + self.mgr = VirtualBoxManager(None, None) + self.vbox = self.mgr.getVirtualBox() + self.machine = self.vbox.findMachine(self.vm_name) + self.session = None + self.console = None + self.guest = None + self._ssh_client = None + self._cancelled = threading.Event() + + def set_cancel_event(self, event: threading.Event): + """设置外部取消事件,与 GUI 层的取消信号同步""" + self._cancelled = event + + def cancel_operations(self): + """标记取消所有正在进行的操作""" + self._cancelled.set() + # 强制关闭 SSH 连接,让阻塞的操作立即返回 + if self._ssh_client: + try: + self._ssh_client.close() + except: + pass + self._ssh_client = None + + def reset_cancel(self): + """重置取消标记""" + self._cancelled.clear() + + def is_cancelled(self): + """检查是否已取消""" + return self._cancelled.is_set() + + # ---------- 虚拟机生命周期 ---------- + def is_running(self) -> bool: + """检查虚拟机是否正在运行""" + try: + state = self.machine.state + return state == self.mgr.constants.MachineState_Running + except Exception as e: + print(f"检查虚拟机状态失败: {e}") + return False + + def start_vm(self, wait_for_ssh=True): + """启动虚拟机,并返回是否成功。如果虚拟机已在运行则跳过。""" + if self.is_running(): + print("✔ 虚拟机已在运行,跳过启动") + # 等待 SSH 就绪 + if wait_for_ssh and not self._wait_for_ssh_ready(self.ssh_timeout): + print("SSH 服务在超时时间内未就绪") + return False + return True + + if self._cancelled.is_set(): + return False + + self.session = self.mgr.getSessionObject(self.vbox) + progress = self.machine.launchVMProcess(self.session, "headless", []) + progress.waitForCompletion(-1) + if progress.resultCode != 0: + error = progress.errorInfo.text if progress.errorInfo else "Unknown" + print(f"启动失败: {error}") + self.session.unlockMachine() + self.session = None + return False + + self.console = self.session.console + self.guest = self.console.guest + + # 等待 Running 状态 + if not self._wait_for_vm_state(self.mgr.constants.MachineState_Running, 120): + print("虚拟机未能进入 Running 状态") + self.stop_vm() + return False + print("✔ 虚拟机已运行") + + if self._cancelled.is_set(): + return False + + # 等待 Guest Additions 基本就绪 + self._wait_for_guest_ready(60) + print("等待 SSH 服务启动...") + time.sleep(15) + + if wait_for_ssh and not self._wait_for_ssh_ready(self.ssh_timeout): + print("SSH 服务在超时时间内未就绪") + self.stop_vm() + return False + + return True + + def stop_vm(self): + """关闭虚拟机并清理会话""" + # 先关闭 SSH 连接 + if self._ssh_client: + try: + self._ssh_client.close() + except: + pass + self._ssh_client = None + + # 如果虚拟机没在运行,只需要解锁会话 + if not self.is_running(): + print("虚拟机未运行,跳过关机") + if self.session: + try: + self.session.unlockMachine() + except: + pass + self.session = None + return + + if self.console: + print("正在关闭虚拟机...") + try: + self.console.powerButton() + except: + pass + + # 等待虚拟机进入 Stopped 状态 + self._wait_for_vm_state(self.mgr.constants.MachineState_Stopped, 30) + + if self.session: + try: + self.session.unlockMachine() + except: + pass + self.session = None + + print("虚拟机已关闭") + + def ensure_vm_stopped(self): + """确保虚拟机已关闭(供启动前调用)""" + if self.is_running(): + print("检测到虚拟机正在运行,正在关闭...") + self.stop_vm() + # 等待完全停止 + time.sleep(2) + return True + return False + + # ---------- SSH 连接管理 ---------- + def get_ssh_client(self): + """获取或创建 SSH 连接(带重试)""" + if self._ssh_client is not None: + return self._ssh_client + + for i in range(5): + if self._cancelled.is_set(): + return None + if not self._is_port_open(self.ssh_host, self.ssh_port): + print(f"SSH 端口 {self.ssh_host}:{self.ssh_port} 不可达,重试 {i+1}/5") + time.sleep(10) + continue + try: + client = paramiko.SSHClient() + client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + client.connect( + self.ssh_host, + port=self.ssh_port, + username=self.ssh_user, + password=self.ssh_pass, + timeout=10, + ) + self._ssh_client = client + return client + except Exception as e: + print(f"SSH 连接失败 ({i+1}/5): {e}") + if self._cancelled.is_set(): + return None + time.sleep(10) + return None + + def close_ssh(self): + """关闭 SSH 连接""" + if self._ssh_client: + self._ssh_client.close() + self._ssh_client = None + + # ---------- 核心功能 ---------- + def exec_command(self, command, get_pty=False): + """ + 执行命令并返回 (stdout_str, stderr_str, exit_code) + 边执行边打印输出,方便实时查看进度 + 如果连接失败返回 (None, None, -1) + """ + client = self.get_ssh_client() + if not client: + return None, None, -1 + try: + stdin, stdout, stderr = client.exec_command( + command, timeout=self.cmd_timeout, get_pty=get_pty + ) + + # 实时读取并打印 stdout + out_lines = [] + for line in stdout: + if self._cancelled.is_set(): + # 取消时主动关闭通道 + stdout.channel.close() + break + if isinstance(line, bytes): + decoded = line.decode("utf-8", errors="replace").rstrip("\n") + else: + decoded = str(line).rstrip("\n") + print(f" {decoded}") + out_lines.append(decoded) + + # 实时读取并打印 stderr + err_lines = [] + for line in stderr: + if self._cancelled.is_set(): + stderr.channel.close() + break + if isinstance(line, bytes): + decoded = line.decode("utf-8", errors="replace").rstrip("\n") + else: + decoded = str(line).rstrip("\n") + print(f" [stderr] {decoded}") + err_lines.append(decoded) + + out = "\n".join(out_lines) + err = "\n".join(err_lines) + exit_code = stdout.channel.recv_exit_status() + return out, err, exit_code + except Exception as e: + if self._cancelled.is_set(): + return None, None, -1 + print(f"执行命令失败: {e}") + return None, None, -1 + + def upload_file(self, local_path, remote_path): + """上传本地文件到虚拟机,返回是否成功""" + client = self.get_ssh_client() + if not client: + return False + try: + sftp = client.open_sftp() + sftp.put(local_path, remote_path) + sftp.close() + print(f"✔ 上传成功: {local_path} -> {remote_path}") + return True + except Exception as e: + if self._cancelled.is_set(): + return False + print(f"✘ 上传失败: {e}") + return False + + def download_file(self, remote_path, local_path): + """从虚拟机下载文件到本地,返回是否成功""" + client = self.get_ssh_client() + if not client: + return False + try: + sftp = client.open_sftp() + sftp.get(remote_path, local_path) + sftp.close() + print(f"✔ 下载成功: {remote_path} -> {local_path}") + return True + except Exception as e: + if self._cancelled.is_set(): + return False + print(f"✘ 下载失败: {e}") + return False + + def upload_directory(self, local_dir, remote_dir): + """递归上传整个文件夹到远程服务器,返回是否成功""" + import os as _os + + client = self.get_ssh_client() + if not client: + return False + try: + sftp = client.open_sftp() + local_dir = _os.path.normpath(local_dir) + remote_dir = remote_dir.rstrip("/") + # 展开 ~ 为真实路径(SFTP 不会自动展开 ~) + if remote_dir.startswith("~/"): + out, _, _ = self.exec_command("echo ~") + if out and out.strip(): + remote_dir = out.strip() + remote_dir[1:] + self.exec_command(f'mkdir -p "{remote_dir}"') + for root, dirs, files in _os.walk(local_dir): + rel_path = _os.path.relpath(root, local_dir) + if rel_path == ".": + rel_path = "" + target_dir = remote_dir + ( + "/" + rel_path.replace("\\", "/") if rel_path else "" + ) + self.exec_command(f'mkdir -p "{target_dir}"') + for fname in files: + if self._cancelled.is_set(): + sftp.close() + return False + local_file = _os.path.join(root, fname) + remote_file = target_dir + "/" + fname + sftp.put(local_file, remote_file) + print(f" ✔ {local_file} -> {remote_file}") + sftp.close() + print(f"✔ 文件夹上传成功: {local_dir} -> {remote_dir}") + return True + except Exception as e: + if self._cancelled.is_set(): + return False + print(f"✘ 文件夹上传失败: {e}") + return False + + # ---------- 内部辅助方法 ---------- + def _wait_for_vm_state(self, target_state, timeout=120): + start = time.time() + while time.time() - start < timeout: + if self._cancelled.is_set(): + return False + try: + if self.console.state == target_state: # type: ignore + return True + except: + pass + time.sleep(1) + return False + + def _wait_for_guest_ready(self, timeout=60): + start = time.time() + while time.time() - start < timeout: + if self._cancelled.is_set(): + return False + try: + if ( + self.guest.additionsRunLevel # type: ignore + >= self.mgr.constants.AdditionsRunLevelType_System + ): + return True + except: + pass + time.sleep(2) + return False + + def _wait_for_ssh_ready(self, timeout=180): + """等待 SSH 端口可连接""" + start = time.time() + while time.time() - start < timeout: + if self._cancelled.is_set(): + return False + if self._is_port_open(self.ssh_host, self.ssh_port): + return True + time.sleep(2) + return False + + @staticmethod + def _is_port_open(host, port, timeout=5): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(timeout) + try: + s.connect((host, port)) + s.close() + return True + except: + return False diff --git a/src/virtualbox_client/utils/vm_agent.py b/src/virtualbox_client/utils/vm_agent.py new file mode 100644 index 0000000..e9f059c --- /dev/null +++ b/src/virtualbox_client/utils/vm_agent.py @@ -0,0 +1,336 @@ +import json +import os +import time +from openai import OpenAI +from .vbox_ssh_manager import VBoxSSHManager +from .ssh_manager import SSHManager +from typing import Union + + +class VMAgent: + """大模型驱动的虚拟机自动化 Agent""" + + TOOLS = [ + { + "type": "function", + "function": { + "name": "run_command", + "description": "在虚拟机中执行一条 shell 命令并等待执行完成(如需 sudo,在命令中使用 $SUDO_PASSWORD 占位符,例如 'echo $SUDO_PASSWORD | sudo -S apt update')。重要:所有命令必须是非交互式的,对于 apt install、add-apt-repository 等一定记得加 -y 参数,否则命令会卡住。不要用此工具启动后台服务(如 Web 服务器),请用 start_daemon。", + "parameters": { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "要执行的命令,例如 'ls -la /home';如需提权,用 echo $SUDO_PASSWORD | sudo -S <命令> 的格式。重要:命令必须是非交互式的,apt install/add-apt-repository 等必须加 -y 参数!", + }, + "get_pty": { + "type": "boolean", + "description": "是否需要伪终端(如交互式 sudo),默认 false;用 echo $SUDO_PASSWORD | sudo -S 的方式通常不需要", + }, + }, + "required": ["command"], + }, + }, + }, + { + "type": "function", + "function": { + "name": "start_daemon", + "description": "在虚拟机中以后台守护进程方式启动一个服务(如 Web 服务器),不会阻塞后续命令。stdout/stderr 将重定向到日志文件,可通过 check_daemon 查看状态。", + "parameters": { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "要以后台服务方式运行的命令,例如 'cd /home/chakcy/chongming && uv run serve'。不要加 & 或 nohup,系统会自动处理。", + }, + "logfile": { + "type": "string", + "description": "日志文件路径,例如 '/tmp/serve.log'。该文件会包含服务的 stdout 和 stderr 输出。", + }, + "workdir": { + "type": "string", + "description": "工作目录,可选。例如 '/home/chakcy/chongming'。", + }, + }, + "required": ["command", "logfile"], + }, + }, + }, + { + "type": "function", + "function": { + "name": "check_daemon", + "description": "检查后台服务的运行状态和最近日志。如果服务正在运行,会返回最后 N 行日志;如果已退出,会返回退出码和完整日志。", + "parameters": { + "type": "object", + "properties": { + "pid": { + "type": "string", + "description": "后台进程的 PID(来自 start_daemon 的返回结果)", + }, + "logfile": { + "type": "string", + "description": "日志文件路径(来自 start_daemon 时指定的 logfile)", + }, + }, + "required": ["pid", "logfile"], + }, + }, + }, + { + "type": "function", + "function": { + "name": "stop_daemon", + "description": "停止一个后台服务进程", + "parameters": { + "type": "object", + "properties": { + "pid": { + "type": "string", + "description": "要停止的后台进程 PID", + }, + }, + "required": ["pid"], + }, + }, + }, + { + "type": "function", + "function": { + "name": "upload_file", + "description": "上传本地文件到虚拟机", + "parameters": { + "type": "object", + "properties": { + "local_path": {"type": "string"}, + "remote_path": {"type": "string"}, + }, + "required": ["local_path", "remote_path"], + }, + }, + }, + { + "type": "function", + "function": { + "name": "download_file", + "description": "从虚拟机下载文件到本地", + "parameters": { + "type": "object", + "properties": { + "remote_path": {"type": "string"}, + "local_path": {"type": "string"}, + }, + "required": ["remote_path", "local_path"], + }, + }, + }, + { + "type": "function", + "function": { + "name": "finish_task", + "description": "任务完成,返回结果总结", + "parameters": { + "type": "object", + "properties": { + "summary": { + "type": "string", + "description": "任务执行总结", + } + }, + "required": ["summary"], + }, + }, + }, + ] + + def __init__( + self, + vm_manager: Union[VBoxSSHManager, SSHManager], + api_key=None, + base_url=None, + model="gpt-4o", + sudo_password=None, + ): + self.vm = vm_manager + self.client = OpenAI( + api_key=api_key or os.getenv("LLM_API_KEY"), + base_url=base_url or os.getenv("LLM_BASE_URL"), + ) + self.model = model or os.getenv("LLM_MODEL", "gpt-4o") + self.sudo_password = sudo_password or os.getenv("VM_SSH_PASS") + # 记录虚拟机是否是由本任务启动的(True=我们启动的,False=执行前已在运行) + self.vm_started_by_us = False + + def run(self, task: str) -> tuple: + """ + 执行一个自然语言任务,返回 (summary, vm_started_by_us) + vm_started_by_us 表示虚拟机是否是由本次任务启动的(供 GUI 决定是否关机) + """ + system_prompt = ( + "你是一个 VirtualBox 虚拟机管理助手。" + "你可以通过提供的工具在 Ubuntu 20.04 虚拟机中执行操作。" + "请一步步完成任务,每次只调用一个工具,观察结果后决定下一步。" + "不要同时调用多个工具。" + "完成任务后调用 finish_task 总结。" + "\n\n重要: 当需要 sudo 提权时,请在命令中使用 $SUDO_PASSWORD 占位符," + "例如: 'echo $SUDO_PASSWORD | sudo -S apt update'" + "这样系统会自动替换为正确的密码。不要试图用别的方式传密码。" + "\n\n重要: 所有命令必须是非交互式的,因为 SSH 无法处理交互式输入。" + "对于 apt、add-apt-repository 等可能弹出确认提示的命令,务必加上 -y 标志。" + "例如,使用 'add-apt-repository -y ' 而不是 'add-apt-repository '," + "使用 'apt install -y <包名>' 而不是 'apt install <包名>'。" + "使用 'apt-get install -y <包名>' 而不是 'apt-get install <包名>'。" + ) + messages = [ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": task}, + ] + + # 判断虚拟机是否已在运行:如果已在运行则直接 SSH 连接,不重启 + # start_vm() 内部已处理「如果已在运行则跳过启动」的逻辑 + # 但我们还需要知道「是否是本次启动的」,以便让用户决定是否关机 + was_running_before = self.vm.is_running() + if was_running_before: + print("✔ 检测到虚拟机已在运行,直接进行 SSH 连接") + else: + print("🔌 虚拟机未运行,需要启动") + + if not self.vm.start_vm(): + return "❌ 虚拟机启动失败,无法执行任务", False + + # 记录虚拟机是否是由我们启动的 + self.vm_started_by_us = not was_running_before + + try: + while True: + if self.vm.is_cancelled(): + return "⏹️ 任务已取消", self.vm_started_by_us + + response = self.client.chat.completions.create( + model=self.model, + messages=messages, # type: ignore + tools=self.TOOLS, # type: ignore + tool_choice="auto", + ) + msg = response.choices[0].message + + # 输出模型文本(若有) + if msg.content: + print("🤖", msg.content) + + # 无工具调用即结束对话 + if not msg.tool_calls: + break + + # 先追加 assistant 消息到历史(包含所有 tool_calls) + messages.append(msg) # type: ignore + + # 依次处理每个 tool_call + for tool_call in msg.tool_calls: + if self.vm.is_cancelled(): + return "⏹️ 任务已取消", self.vm_started_by_us + + func_name = tool_call.function.name # type: ignore + func_args = json.loads(tool_call.function.arguments) # type: ignore + + print(f"🔧 执行 {func_name}: {func_args}") + + # finish_task 特殊处理:直接跳出循环,不追加到消息 + if func_name == "finish_task": + summary = func_args.get("summary", "任务完成") + print("✅", summary) + return summary, self.vm_started_by_us + + result = self._execute_tool(func_name, func_args) + + messages.append( + { + "role": "tool", + "tool_call_id": tool_call.id, + "content": result, + } + ) + finally: + # 注意:不再 here 自动关闭 VM! + # VM 的生命周期由 GUI 层根据用户选择来决定是否关闭 + pass + + return "任务执行完成", self.vm_started_by_us + + def stop_vm_if_we_started(self): + """如果 VM 是本任务启动的则关闭,否则保持运行""" + if self.vm_started_by_us: + print("🛑 关闭由本任务启动的虚拟机") + self.vm.stop_vm() + else: + print("⏭️ 虚拟机并非本任务启动,保持运行状态") + + def _execute_tool(self, func_name: str, args: dict) -> str: + if func_name == "run_command": + cmd = args["command"] + cmd = cmd.replace("$SUDO_PASSWORD", self.sudo_password or "") + get_pty = args.get("get_pty", False) + out, err, code = self.vm.exec_command(cmd, get_pty=get_pty) + return f"退出码: {code}\n标准输出:\n{out}\n标准错误:\n{err}" + elif func_name == "start_daemon": + cmd = args["command"] + logfile = args["logfile"] + workdir = args.get("workdir", "") + cmd = cmd.replace("$SUDO_PASSWORD", self.sudo_password or "") + + # 构造后台执行命令:重定向 stdout/stderr 到日志文件 + cd_prefix = f"cd {workdir} && " if workdir else "" + bg_cmd = ( + f"{cd_prefix}nohup bash -c '{cmd}' " f"> {logfile} 2>&1 & echo PID=$!" + ) + out, err, code = self.vm.exec_command(bg_cmd, get_pty=False) + # 提取 PID + pid = "" + if out: + for line in out.splitlines(): + if line.startswith("PID="): + pid = line.split("=", 1)[1].strip() + break + if pid: + return f"后台服务已启动, PID: {pid}, 日志: {logfile}" + else: + return f"启动失败(或未获取到 PID)\n输出: {out}\n错误: {err}" + elif func_name == "check_daemon": + pid = args["pid"] + logfile = args["logfile"] + check_cmd = ( + f"if ps -p {pid} > /dev/null 2>&1; then " + f' echo "STATUS=running"; ' + f' echo "---最后20行日志---"; ' + f' tail -20 "{logfile}" 2>/dev/null || echo "日志文件为空"; ' + f"else " + f' echo "STATUS=exited"; ' + f' echo "---完整日志---"; ' + f' cat "{logfile}" 2>/dev/null || echo "日志文件不存在"; ' + f"fi" + ) + out, err, code = self.vm.exec_command(check_cmd) + return f"{out}\n{err}" if err else out or "无输出" + elif func_name == "stop_daemon": + pid = args["pid"] + stop_cmd = ( + f'kill {pid} 2>/dev/null && echo "已发送 SIGTERM"; ' + f"sleep 2; " + f"if ps -p {pid} > /dev/null 2>&1; then " + f' kill -9 {pid} 2>/dev/null && echo "已强制杀死"; ' + f"fi; " + f'echo "进程 {pid} 已停止"' + ) + out, err, code = self.vm.exec_command(stop_cmd) + return f"{out}\n{err}" if err else out or "无输出" + elif func_name == "upload_file": + success = self.vm.upload_file(args["local_path"], args["remote_path"]) + return "上传成功" if success else "上传失败" + elif func_name == "download_file": + success = self.vm.download_file(args["remote_path"], args["local_path"]) + return "下载成功" if success else "下载失败" + elif func_name == "finish_task": + return args.get("summary", "任务完成") + else: + return f"未知工具: {func_name}" diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..9b0b003 --- /dev/null +++ b/uv.lock @@ -0,0 +1,664 @@ +version = 1 +revision = 3 +requires-python = ">=3.14" + +[[package]] +name = "altgraph" +version = "0.17.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/f8/97fdf103f38fed6792a1601dbc16cc8aac56e7459a9fff08c812d8ae177a/altgraph-0.17.5.tar.gz", hash = "sha256:c87b395dd12fabde9c99573a9749d67da8d29ef9de0125c7f536699b4a9bc9e7", size = 48428, upload-time = "2025-11-21T20:35:50.583Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/ba/000a1996d4308bc65120167c21241a3b205464a2e0b58deda26ae8ac21d1/altgraph-0.17.5-py2.py3-none-any.whl", hash = "sha256:f3a22400bce1b0c701683820ac4f3b159cd301acab067c51c653e06961600597", size = 21228, upload-time = "2025-11-21T20:35:49.444Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, +] + +[[package]] +name = "bcrypt" +version = "5.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/36/3329e2518d70ad8e2e5817d5a4cac6bba05a47767ec416c7d020a965f408/bcrypt-5.0.0.tar.gz", hash = "sha256:f748f7c2d6fd375cc93d3fba7ef4a9e3a092421b8dbf34d8d4dc06be9492dfdd", size = 25386, upload-time = "2025-09-25T19:50:47.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/14/c18006f91816606a4abe294ccc5d1e6f0e42304df5a33710e9e8e95416e1/bcrypt-5.0.0-cp314-cp314t-macosx_10_12_universal2.whl", hash = "sha256:4870a52610537037adb382444fefd3706d96d663ac44cbb2f37e3919dca3d7ef", size = 481862, upload-time = "2025-09-25T19:49:28.365Z" }, + { url = "https://files.pythonhosted.org/packages/67/49/dd074d831f00e589537e07a0725cf0e220d1f0d5d8e85ad5bbff251c45aa/bcrypt-5.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48f753100931605686f74e27a7b49238122aa761a9aefe9373265b8b7aa43ea4", size = 268544, upload-time = "2025-09-25T19:49:30.39Z" }, + { url = "https://files.pythonhosted.org/packages/f5/91/50ccba088b8c474545b034a1424d05195d9fcbaaf802ab8bfe2be5a4e0d7/bcrypt-5.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f70aadb7a809305226daedf75d90379c397b094755a710d7014b8b117df1ebbf", size = 271787, upload-time = "2025-09-25T19:49:32.144Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e7/d7dba133e02abcda3b52087a7eea8c0d4f64d3e593b4fffc10c31b7061f3/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:744d3c6b164caa658adcb72cb8cc9ad9b4b75c7db507ab4bc2480474a51989da", size = 269753, upload-time = "2025-09-25T19:49:33.885Z" }, + { url = "https://files.pythonhosted.org/packages/33/fc/5b145673c4b8d01018307b5c2c1fc87a6f5a436f0ad56607aee389de8ee3/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a28bc05039bdf3289d757f49d616ab3efe8cf40d8e8001ccdd621cd4f98f4fc9", size = 289587, upload-time = "2025-09-25T19:49:35.144Z" }, + { url = "https://files.pythonhosted.org/packages/27/d7/1ff22703ec6d4f90e62f1a5654b8867ef96bafb8e8102c2288333e1a6ca6/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:7f277a4b3390ab4bebe597800a90da0edae882c6196d3038a73adf446c4f969f", size = 272178, upload-time = "2025-09-25T19:49:36.793Z" }, + { url = "https://files.pythonhosted.org/packages/c8/88/815b6d558a1e4d40ece04a2f84865b0fef233513bd85fd0e40c294272d62/bcrypt-5.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:79cfa161eda8d2ddf29acad370356b47f02387153b11d46042e93a0a95127493", size = 269295, upload-time = "2025-09-25T19:49:38.164Z" }, + { url = "https://files.pythonhosted.org/packages/51/8c/e0db387c79ab4931fc89827d37608c31cc57b6edc08ccd2386139028dc0d/bcrypt-5.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a5393eae5722bcef046a990b84dff02b954904c36a194f6cfc817d7dca6c6f0b", size = 271700, upload-time = "2025-09-25T19:49:39.917Z" }, + { url = "https://files.pythonhosted.org/packages/06/83/1570edddd150f572dbe9fc00f6203a89fc7d4226821f67328a85c330f239/bcrypt-5.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7f4c94dec1b5ab5d522750cb059bb9409ea8872d4494fd152b53cca99f1ddd8c", size = 334034, upload-time = "2025-09-25T19:49:41.227Z" }, + { url = "https://files.pythonhosted.org/packages/c9/f2/ea64e51a65e56ae7a8a4ec236c2bfbdd4b23008abd50ac33fbb2d1d15424/bcrypt-5.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0cae4cb350934dfd74c020525eeae0a5f79257e8a201c0c176f4b84fdbf2a4b4", size = 352766, upload-time = "2025-09-25T19:49:43.08Z" }, + { url = "https://files.pythonhosted.org/packages/d7/d4/1a388d21ee66876f27d1a1f41287897d0c0f1712ef97d395d708ba93004c/bcrypt-5.0.0-cp314-cp314t-win32.whl", hash = "sha256:b17366316c654e1ad0306a6858e189fc835eca39f7eb2cafd6aaca8ce0c40a2e", size = 152449, upload-time = "2025-09-25T19:49:44.971Z" }, + { url = "https://files.pythonhosted.org/packages/3f/61/3291c2243ae0229e5bca5d19f4032cecad5dfb05a2557169d3a69dc0ba91/bcrypt-5.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:92864f54fb48b4c718fc92a32825d0e42265a627f956bc0361fe869f1adc3e7d", size = 149310, upload-time = "2025-09-25T19:49:46.162Z" }, + { url = "https://files.pythonhosted.org/packages/3e/89/4b01c52ae0c1a681d4021e5dd3e45b111a8fb47254a274fa9a378d8d834b/bcrypt-5.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dd19cf5184a90c873009244586396a6a884d591a5323f0e8a5922560718d4993", size = 143761, upload-time = "2025-09-25T19:49:47.345Z" }, + { url = "https://files.pythonhosted.org/packages/84/29/6237f151fbfe295fe3e074ecc6d44228faa1e842a81f6d34a02937ee1736/bcrypt-5.0.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:fc746432b951e92b58317af8e0ca746efe93e66555f1b40888865ef5bf56446b", size = 494553, upload-time = "2025-09-25T19:49:49.006Z" }, + { url = "https://files.pythonhosted.org/packages/45/b6/4c1205dde5e464ea3bd88e8742e19f899c16fa8916fb8510a851fae985b5/bcrypt-5.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c2388ca94ffee269b6038d48747f4ce8df0ffbea43f31abfa18ac72f0218effb", size = 275009, upload-time = "2025-09-25T19:49:50.581Z" }, + { url = "https://files.pythonhosted.org/packages/3b/71/427945e6ead72ccffe77894b2655b695ccf14ae1866cd977e185d606dd2f/bcrypt-5.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:560ddb6ec730386e7b3b26b8b4c88197aaed924430e7b74666a586ac997249ef", size = 278029, upload-time = "2025-09-25T19:49:52.533Z" }, + { url = "https://files.pythonhosted.org/packages/17/72/c344825e3b83c5389a369c8a8e58ffe1480b8a699f46c127c34580c4666b/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d79e5c65dcc9af213594d6f7f1fa2c98ad3fc10431e7aa53c176b441943efbdd", size = 275907, upload-time = "2025-09-25T19:49:54.709Z" }, + { url = "https://files.pythonhosted.org/packages/0b/7e/d4e47d2df1641a36d1212e5c0514f5291e1a956a7749f1e595c07a972038/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2b732e7d388fa22d48920baa267ba5d97cca38070b69c0e2d37087b381c681fd", size = 296500, upload-time = "2025-09-25T19:49:56.013Z" }, + { url = "https://files.pythonhosted.org/packages/0f/c3/0ae57a68be2039287ec28bc463b82e4b8dc23f9d12c0be331f4782e19108/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0c8e093ea2532601a6f686edbc2c6b2ec24131ff5c52f7610dd64fa4553b5464", size = 278412, upload-time = "2025-09-25T19:49:57.356Z" }, + { url = "https://files.pythonhosted.org/packages/45/2b/77424511adb11e6a99e3a00dcc7745034bee89036ad7d7e255a7e47be7d8/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5b1589f4839a0899c146e8892efe320c0fa096568abd9b95593efac50a87cb75", size = 275486, upload-time = "2025-09-25T19:49:59.116Z" }, + { url = "https://files.pythonhosted.org/packages/43/0a/405c753f6158e0f3f14b00b462d8bca31296f7ecfc8fc8bc7919c0c7d73a/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:89042e61b5e808b67daf24a434d89bab164d4de1746b37a8d173b6b14f3db9ff", size = 277940, upload-time = "2025-09-25T19:50:00.869Z" }, + { url = "https://files.pythonhosted.org/packages/62/83/b3efc285d4aadc1fa83db385ec64dcfa1707e890eb42f03b127d66ac1b7b/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:e3cf5b2560c7b5a142286f69bde914494b6d8f901aaa71e453078388a50881c4", size = 310776, upload-time = "2025-09-25T19:50:02.393Z" }, + { url = "https://files.pythonhosted.org/packages/95/7d/47ee337dacecde6d234890fe929936cb03ebc4c3a7460854bbd9c97780b8/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f632fd56fc4e61564f78b46a2269153122db34988e78b6be8b32d28507b7eaeb", size = 312922, upload-time = "2025-09-25T19:50:04.232Z" }, + { url = "https://files.pythonhosted.org/packages/d6/3a/43d494dfb728f55f4e1cf8fd435d50c16a2d75493225b54c8d06122523c6/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:801cad5ccb6b87d1b430f183269b94c24f248dddbbc5c1f78b6ed231743e001c", size = 341367, upload-time = "2025-09-25T19:50:05.559Z" }, + { url = "https://files.pythonhosted.org/packages/55/ab/a0727a4547e383e2e22a630e0f908113db37904f58719dc48d4622139b5c/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3cf67a804fc66fc217e6914a5635000259fbbbb12e78a99488e4d5ba445a71eb", size = 359187, upload-time = "2025-09-25T19:50:06.916Z" }, + { url = "https://files.pythonhosted.org/packages/1b/bb/461f352fdca663524b4643d8b09e8435b4990f17fbf4fea6bc2a90aa0cc7/bcrypt-5.0.0-cp38-abi3-win32.whl", hash = "sha256:3abeb543874b2c0524ff40c57a4e14e5d3a66ff33fb423529c88f180fd756538", size = 153752, upload-time = "2025-09-25T19:50:08.515Z" }, + { url = "https://files.pythonhosted.org/packages/41/aa/4190e60921927b7056820291f56fc57d00d04757c8b316b2d3c0d1d6da2c/bcrypt-5.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:35a77ec55b541e5e583eb3436ffbbf53b0ffa1fa16ca6782279daf95d146dcd9", size = 150881, upload-time = "2025-09-25T19:50:09.742Z" }, + { url = "https://files.pythonhosted.org/packages/54/12/cd77221719d0b39ac0b55dbd39358db1cd1246e0282e104366ebbfb8266a/bcrypt-5.0.0-cp38-abi3-win_arm64.whl", hash = "sha256:cde08734f12c6a4e28dc6755cd11d3bdfea608d93d958fffbe95a7026ebe4980", size = 144931, upload-time = "2025-09-25T19:50:11.016Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ba/2af136406e1c3839aea9ecadc2f6be2bcd1eff255bd451dd39bcf302c47a/bcrypt-5.0.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0c418ca99fd47e9c59a301744d63328f17798b5947b0f791e9af3c1c499c2d0a", size = 495313, upload-time = "2025-09-25T19:50:12.309Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ee/2f4985dbad090ace5ad1f7dd8ff94477fe089b5fab2040bd784a3d5f187b/bcrypt-5.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddb4e1500f6efdd402218ffe34d040a1196c072e07929b9820f363a1fd1f4191", size = 275290, upload-time = "2025-09-25T19:50:13.673Z" }, + { url = "https://files.pythonhosted.org/packages/e4/6e/b77ade812672d15cf50842e167eead80ac3514f3beacac8902915417f8b7/bcrypt-5.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7aeef54b60ceddb6f30ee3db090351ecf0d40ec6e2abf41430997407a46d2254", size = 278253, upload-time = "2025-09-25T19:50:15.089Z" }, + { url = "https://files.pythonhosted.org/packages/36/c4/ed00ed32f1040f7990dac7115f82273e3c03da1e1a1587a778d8cea496d8/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f0ce778135f60799d89c9693b9b398819d15f1921ba15fe719acb3178215a7db", size = 276084, upload-time = "2025-09-25T19:50:16.699Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c4/fa6e16145e145e87f1fa351bbd54b429354fd72145cd3d4e0c5157cf4c70/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a71f70ee269671460b37a449f5ff26982a6f2ba493b3eabdd687b4bf35f875ac", size = 297185, upload-time = "2025-09-25T19:50:18.525Z" }, + { url = "https://files.pythonhosted.org/packages/24/b4/11f8a31d8b67cca3371e046db49baa7c0594d71eb40ac8121e2fc0888db0/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8429e1c410b4073944f03bd778a9e066e7fad723564a52ff91841d278dfc822", size = 278656, upload-time = "2025-09-25T19:50:19.809Z" }, + { url = "https://files.pythonhosted.org/packages/ac/31/79f11865f8078e192847d2cb526e3fa27c200933c982c5b2869720fa5fce/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:edfcdcedd0d0f05850c52ba3127b1fce70b9f89e0fe5ff16517df7e81fa3cbb8", size = 275662, upload-time = "2025-09-25T19:50:21.567Z" }, + { url = "https://files.pythonhosted.org/packages/d4/8d/5e43d9584b3b3591a6f9b68f755a4da879a59712981ef5ad2a0ac1379f7a/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:611f0a17aa4a25a69362dcc299fda5c8a3d4f160e2abb3831041feb77393a14a", size = 278240, upload-time = "2025-09-25T19:50:23.305Z" }, + { url = "https://files.pythonhosted.org/packages/89/48/44590e3fc158620f680a978aafe8f87a4c4320da81ed11552f0323aa9a57/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:db99dca3b1fdc3db87d7c57eac0c82281242d1eabf19dcb8a6b10eb29a2e72d1", size = 311152, upload-time = "2025-09-25T19:50:24.597Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/e4fbfc46f14f47b0d20493669a625da5827d07e8a88ee460af6cd9768b44/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:5feebf85a9cefda32966d8171f5db7e3ba964b77fdfe31919622256f80f9cf42", size = 313284, upload-time = "2025-09-25T19:50:26.268Z" }, + { url = "https://files.pythonhosted.org/packages/25/ae/479f81d3f4594456a01ea2f05b132a519eff9ab5768a70430fa1132384b1/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3ca8a166b1140436e058298a34d88032ab62f15aae1c598580333dc21d27ef10", size = 341643, upload-time = "2025-09-25T19:50:28.02Z" }, + { url = "https://files.pythonhosted.org/packages/df/d2/36a086dee1473b14276cd6ea7f61aef3b2648710b5d7f1c9e032c29b859f/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:61afc381250c3182d9078551e3ac3a41da14154fbff647ddf52a769f588c4172", size = 359698, upload-time = "2025-09-25T19:50:31.347Z" }, + { url = "https://files.pythonhosted.org/packages/c0/f6/688d2cd64bfd0b14d805ddb8a565e11ca1fb0fd6817175d58b10052b6d88/bcrypt-5.0.0-cp39-abi3-win32.whl", hash = "sha256:64d7ce196203e468c457c37ec22390f1a61c85c6f0b8160fd752940ccfb3a683", size = 153725, upload-time = "2025-09-25T19:50:34.384Z" }, + { url = "https://files.pythonhosted.org/packages/9f/b9/9d9a641194a730bda138b3dfe53f584d61c58cd5230e37566e83ec2ffa0d/bcrypt-5.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:64ee8434b0da054d830fa8e89e1c8bf30061d539044a39524ff7dec90481e5c2", size = 150912, upload-time = "2025-09-25T19:50:35.69Z" }, + { url = "https://files.pythonhosted.org/packages/27/44/d2ef5e87509158ad2187f4dd0852df80695bb1ee0cfe0a684727b01a69e0/bcrypt-5.0.0-cp39-abi3-win_arm64.whl", hash = "sha256:f2347d3534e76bf50bca5500989d6c1d05ed64b440408057a37673282c654927", size = 144953, upload-time = "2025-09-25T19:50:37.32Z" }, +] + +[[package]] +name = "certifi" +version = "2026.4.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "cryptography" +version = "48.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/a9/db8f313fdcd85d767d4973515e1db101f9c71f95fced83233de224673757/cryptography-48.0.0.tar.gz", hash = "sha256:5c3932f4436d1cccb036cb0eaef46e6e2db91035166f1ad6505c3c9d5a635920", size = 832984, upload-time = "2026-05-04T22:59:38.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/3d/01f6dd9190170a5a241e0e98c2d04be3664a9e6f5b9b872cde63aff1c3dd/cryptography-48.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:0c558d2cdffd8f4bbb30fc7134c74d2ca9a476f830bb053074498fbc86f41ed6", size = 8001587, upload-time = "2026-05-04T22:57:36.803Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6e/e90527eef33f309beb811cf7c982c3aeffcce8e3edb178baa4ca3ae4a6fa/cryptography-48.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f5333311663ea94f75dd408665686aaf426563556bb5283554a3539177e03b8c", size = 4690433, upload-time = "2026-05-04T22:57:40.373Z" }, + { url = "https://files.pythonhosted.org/packages/90/04/673510ed51ddff56575f306cf1617d80411ee76831ccd3097599140efdfe/cryptography-48.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7995ef305d7165c3f11ae07f2517e5a4f1d5c18da1376a0a9ed496336b69e5f3", size = 4710620, upload-time = "2026-05-04T22:57:42.935Z" }, + { url = "https://files.pythonhosted.org/packages/14/d5/e9c4ef932c8d800490c34d8bd589d64a31d5890e27ec9e9ad532be893294/cryptography-48.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:40ba1f85eaa6959837b1d51c9767e230e14612eea4ef110ee8854ada22da1bf5", size = 4696283, upload-time = "2026-05-04T22:57:45.294Z" }, + { url = "https://files.pythonhosted.org/packages/0c/29/174b9dfb60b12d59ecfc6cfa04bc88c21b42a54f01b8aae09bb6e51e4c7f/cryptography-48.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:369a6348999f94bbd53435c894377b20ab95f25a9065c283570e70150d8abc3c", size = 5296573, upload-time = "2026-05-04T22:57:47.933Z" }, + { url = "https://files.pythonhosted.org/packages/95/38/0d29a6fd7d0d1373f0c0c88a04ba20e359b257753ac497564cd660fc1d55/cryptography-48.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a0e692c683f4df67815a2d258b324e66f4738bd7a96a218c826dce4f4bd05d8f", size = 4743677, upload-time = "2026-05-04T22:57:50.067Z" }, + { url = "https://files.pythonhosted.org/packages/30/be/eef653013d5c63b6a490529e0316f9ac14a37602965d4903efed1399f32b/cryptography-48.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:18349bbc56f4743c8b12dc32e2bccb2cf83ee8b69a3bba74ef8ae857e26b3d25", size = 4330808, upload-time = "2026-05-04T22:57:52.301Z" }, + { url = "https://files.pythonhosted.org/packages/84/9e/500463e87abb7a0a0f9f256ec21123ecde0a7b5541a15e840ea54551fd81/cryptography-48.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e8eac43dfca5c4cccc6dad9a80504436fca53bb9bc3100a2386d730fbe6b602", size = 4695941, upload-time = "2026-05-04T22:57:54.603Z" }, + { url = "https://files.pythonhosted.org/packages/e3/dc/7303087450c2ec9e7fbb750e17c2abfbc658f23cbd0e54009509b7cc4091/cryptography-48.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9ccdac7d40688ecb5a3b4a604b8a88c8002e3442d6c60aead1db2a89a041560c", size = 5252579, upload-time = "2026-05-04T22:57:57.207Z" }, + { url = "https://files.pythonhosted.org/packages/d0/c0/7101d3b7215edcdc90c45da544961fd8ed2d6448f77577460fa75a8443f7/cryptography-48.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:bd72e68b06bb1e96913f97dd4901119bc17f39d4586a5adf2d3e47bc2b9d58b5", size = 4743326, upload-time = "2026-05-04T22:57:59.535Z" }, + { url = "https://files.pythonhosted.org/packages/ac/d8/5b833bad13016f562ab9d063d68199a4bd121d18458e439515601d3357ec/cryptography-48.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:59baa2cb386c4f0b9905bd6eb4c2a79a69a128408fd31d32ca4d7102d4156321", size = 4826672, upload-time = "2026-05-04T22:58:01.996Z" }, + { url = "https://files.pythonhosted.org/packages/98/e1/7074eb8bf3c135558c73fc2bcf0f5633f912e6fb87e868a55c454080ef09/cryptography-48.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9249e3cd978541d665967ac2cb2787fd6a62bddf1e75b3e347a594d7dacf4f74", size = 4972574, upload-time = "2026-05-04T22:58:03.968Z" }, + { url = "https://files.pythonhosted.org/packages/04/70/e5a1b41d325f797f39427aa44ef8baf0be500065ab6d8e10369d850d4a4f/cryptography-48.0.0-cp311-abi3-win32.whl", hash = "sha256:9c459db21422be75e2809370b829a87eb37f74cd785fc4aa9ea1e5f43b47cda4", size = 3294868, upload-time = "2026-05-04T22:58:06.467Z" }, + { url = "https://files.pythonhosted.org/packages/f4/ac/8ac51b4a5fc5932eb7ee5c517ba7dc8cd834f0048962b6b352f00f41ebf9/cryptography-48.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:5b012212e08b8dd5edc78ef54da83dd9892fd9105323b3993eff6bea65dc21d7", size = 3817107, upload-time = "2026-05-04T22:58:08.845Z" }, + { url = "https://files.pythonhosted.org/packages/6b/84/70e3feea9feea87fd7cbe77efb2712ae1e3e6edf10749dc6e95f4e60e455/cryptography-48.0.0-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:3cb07a3ed6431663cd321ea8a000a1314c74211f823e4177fefa2255e057d1ec", size = 7986556, upload-time = "2026-05-04T22:58:11.172Z" }, + { url = "https://files.pythonhosted.org/packages/89/6e/18e07a618bb5442ba10cf4df16e99c071365528aa570dfcb8c02e25a303b/cryptography-48.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c7378637d7d88016fa6791c159f698b3d3eed28ebf844ac36b9dc04a14dae18", size = 4684776, upload-time = "2026-05-04T22:58:13.712Z" }, + { url = "https://files.pythonhosted.org/packages/be/6a/4ea3b4c6c6759794d5ee2103c304a5076dc4b19ae1f9fe47dba439e159e9/cryptography-48.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc90c0b39b2e3c65ef52c804b72e3c58f8a04ab2a1871272798e5f9572c17d20", size = 4698121, upload-time = "2026-05-04T22:58:16.448Z" }, + { url = "https://files.pythonhosted.org/packages/2f/59/6ff6ad6cae03bb887da2a5860b2c9805f8dac969ef01ce563336c49bd1d1/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:76341972e1eff8b4bea859f09c0d3e64b96ce931b084f9b9b7db8ef364c30eff", size = 4690042, upload-time = "2026-05-04T22:58:18.544Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b4/fc334ed8cfd705aca282fe4d8f5ae64a8e0f74932e9feecb344610cf6e4d/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:55b7718303bf06a5753dcdccf2f3945cf18ad7bffde41b61226e4db31ab89a9c", size = 5282526, upload-time = "2026-05-04T22:58:20.75Z" }, + { url = "https://files.pythonhosted.org/packages/11/08/9f8c5386cc4cd90d8255c7cdd0f5baf459a08502a09de30dc51f553d38dc/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:a64697c641c7b1b2178e573cbc31c7c6684cd56883a478d75143dbb7118036db", size = 4733116, upload-time = "2026-05-04T22:58:23.627Z" }, + { url = "https://files.pythonhosted.org/packages/b8/77/99307d7574045699f8805aa500fa0fb83422d115b5400a064ddd306d7750/cryptography-48.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:561215ea3879cb1cbbf272867e2efda62476f240fb58c64de6b393ae19246741", size = 4316030, upload-time = "2026-05-04T22:58:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/fd/36/a608b98337af3cb2aff4818e406649d30572b7031918b04c87d979495348/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ad64688338ed4bc1a6618076ba75fd7194a5f1797ac60b47afe926285adb3166", size = 4689640, upload-time = "2026-05-04T22:58:27.747Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a6/825010a291b4438aecc1f568bc428189fc1175515223632477c07dc0a6df/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:906cbf0670286c6e0044156bc7d4af9cbb0ef6db9f73e52c3ec56ba6bdde5336", size = 5237657, upload-time = "2026-05-04T22:58:29.848Z" }, + { url = "https://files.pythonhosted.org/packages/b9/09/4e76a09b4caa29aad535ddc806f5d4c5d01885bd978bd984fbc6ca032cae/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:ea8990436d914540a40ab24b6a77c0969695ed52f4a4874c5137ccf7045a7057", size = 4732362, upload-time = "2026-05-04T22:58:32.009Z" }, + { url = "https://files.pythonhosted.org/packages/18/78/444fa04a77d0cb95f417dda20d450e13c56ba8e5220fc892a1658f44f882/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c18684a7f0cc9a3cb60328f496b8e3372def7c5d2df39ac267878b05565aaaae", size = 4819580, upload-time = "2026-05-04T22:58:34.254Z" }, + { url = "https://files.pythonhosted.org/packages/38/85/ea67067c70a1fd4be2c63d35eeed82658023021affccc7b17705f8527dd2/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9be5aafa5736574f8f15f262adc81b2a9869e2cfe9014d52a44633905b40d52c", size = 4963283, upload-time = "2026-05-04T22:58:36.376Z" }, + { url = "https://files.pythonhosted.org/packages/75/54/cc6d0f3deac3e81c7f847e8a189a12b6cdd65059b43dad25d4316abd849a/cryptography-48.0.0-cp314-cp314t-win32.whl", hash = "sha256:c17dfe85494deaeddc5ce251aebd1d60bbe6afc8b62071bb0b469431a000124f", size = 3270954, upload-time = "2026-05-04T22:58:38.791Z" }, + { url = "https://files.pythonhosted.org/packages/49/67/cc947e288c0758a4e5473d1dcb743037ab7785541265a969240b8885441a/cryptography-48.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27241b1dc9962e056062a8eef1991d02c3a24569c95975bd2322a8a52c6e5e12", size = 3797313, upload-time = "2026-05-04T22:58:40.746Z" }, + { url = "https://files.pythonhosted.org/packages/f2/63/61d4a4e1c6b6bab6ce1e213cd36a24c415d90e76d78c5eb8577c5541d2e8/cryptography-48.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:58d00498e8933e4a194f3076aee1b4a97dfec1a6da444535755822fe5d8b0b86", size = 7983482, upload-time = "2026-05-04T22:58:43.769Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ac/f5b5995b87770c693e2596559ffafe195b4033a57f14a82268a2842953f3/cryptography-48.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:614d0949f4790582d2cc25553abd09dd723025f0c0e7c67376a1d77196743d6e", size = 4683266, upload-time = "2026-05-04T22:58:46.064Z" }, + { url = "https://files.pythonhosted.org/packages/ec/c6/8b14f67e18338fbc4adb76f66c001f5c3610b3e2d1837f268f47a347dbbb/cryptography-48.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ce4bfae76319a532a2dc68f82cc32f5676ee792a983187dac07183690e5c66f", size = 4696228, upload-time = "2026-05-04T22:58:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/ea/73/f808fbae9514bd91b47875b003f13e284c8c6bdfd904b7944e803937eec1/cryptography-48.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2eb992bbd4661238c5a397594c83f5b4dc2bc5b848c365c8f991b6780efcc5c7", size = 4689097, upload-time = "2026-05-04T22:58:50.9Z" }, + { url = "https://files.pythonhosted.org/packages/93/01/d86632d7d28db8ae83221995752eeb6639ffb374c2d22955648cf8d52797/cryptography-48.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:22a5cb272895dce158b2cacdfdc3debd299019659f42947dbdac6f32d68fe832", size = 5283582, upload-time = "2026-05-04T22:58:53.017Z" }, + { url = "https://files.pythonhosted.org/packages/02/e1/50edc7a50334807cc4791fc4a0ce7468b4a1416d9138eab358bfc9a3d70b/cryptography-48.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2b4d59804e8408e2fea7d1fbaf218e5ec984325221db76e6a241a9abd6cdd95c", size = 4730479, upload-time = "2026-05-04T22:58:55.611Z" }, + { url = "https://files.pythonhosted.org/packages/6f/af/99a582b1b1641ff5911ac559beb45097cf79efd4ead4657f578ef1af2d47/cryptography-48.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:984a20b0f62a26f48a3396c72e4bc34c66e356d356bf370053066b3b6d54634a", size = 4326481, upload-time = "2026-05-04T22:58:57.607Z" }, + { url = "https://files.pythonhosted.org/packages/90/ee/89aa26a06ef0a7d7611788ffd571a7c50e368cc6a4d5eef8b4884e866edb/cryptography-48.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5a5ed8fde7a1d09376ca0b40e68cd59c69fe23b1f9768bd5824f54681626032a", size = 4688713, upload-time = "2026-05-04T22:59:00.077Z" }, + { url = "https://files.pythonhosted.org/packages/70/ba/bcb1b0bb7a33d4c7c0c4d4c7874b4a62ae4f56113a5f4baefa362dfb1f0f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:8cd666227ef7af430aa5914a9910e0ddd703e75f039cef0825cd0da71b6b711a", size = 5238165, upload-time = "2026-05-04T22:59:02.317Z" }, + { url = "https://files.pythonhosted.org/packages/c9/70/ca4003b1ce5ca3dc3186ada51908c8a9b9ff7d5cab83cc0d43ee14ec144f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9071196d81abc88b3516ac8cdfad32e2b66dd4a5393a8e68a961e9161ddc6239", size = 4729947, upload-time = "2026-05-04T22:59:05.255Z" }, + { url = "https://files.pythonhosted.org/packages/44/a0/4ec7cf774207905aef1a8d11c3750d5a1db805eb380ee4e16df317870128/cryptography-48.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e2d54c8be6152856a36f0882ab231e70f8ec7f14e93cf87db8a2ed056bf160c", size = 4822059, upload-time = "2026-05-04T22:59:07.802Z" }, + { url = "https://files.pythonhosted.org/packages/1e/75/a2e55f99c16fcac7b5d6c1eb19ad8e00799854d6be5ca845f9259eae1681/cryptography-48.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5da777e32ffed6f85a7b2b3f7c5cbc88c146bfcd0a1d7baf5fcc6c52ee35dd4", size = 4960575, upload-time = "2026-05-04T22:59:09.851Z" }, + { url = "https://files.pythonhosted.org/packages/b8/23/6e6f32143ab5d8b36ca848a502c4bcd477ae75b9e1677e3530d669062578/cryptography-48.0.0-cp39-abi3-win32.whl", hash = "sha256:77a2ccbbe917f6710e05ba9adaa25fb5075620bf3ea6fb751997875aff4ae4bd", size = 3279117, upload-time = "2026-05-04T22:59:12.019Z" }, + { url = "https://files.pythonhosted.org/packages/9d/9a/0fea98a70cf1749d41d738836f6349d97945f7c89433a259a6c2642eefeb/cryptography-48.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:16cd65b9330583e4619939b3a3843eec1e6e789744bb01e7c7e2e62e33c239c8", size = 3792100, upload-time = "2026-05-04T22:59:14.884Z" }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "idna" +version = "3.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", size = 194210, upload-time = "2026-04-22T16:42:42.314Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3", size = 68629, upload-time = "2026-04-22T16:42:40.909Z" }, +] + +[[package]] +name = "invoke" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/33/f6/227c48c5fe47fa178ccf1fda8f047d16c97ba926567b661e9ce2045c600c/invoke-3.0.3.tar.gz", hash = "sha256:437b6a622223824380bfb4e64f612711a6b648c795f565efc8625af66fb57f0c", size = 343419, upload-time = "2026-04-07T15:17:48.307Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/de/bbc12563bbf979618d17625a4e753ff7a078523e28d870d3626daa97261a/invoke-3.0.3-py3-none-any.whl", hash = "sha256:f11327165e5cbb89b2ad1d88d3292b5113332c43b8553b494da435d6ec6f5053", size = 160958, upload-time = "2026-04-07T15:17:46.875Z" }, +] + +[[package]] +name = "jiter" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/c1/0cddc6eb17d4c53a99840953f95dd3accdc5cfc7a337b0e9b26476276be9/jiter-0.14.0.tar.gz", hash = "sha256:e8a39e66dac7153cf3f964a12aad515afa8d74938ec5cc0018adcdae5367c79e", size = 165725, upload-time = "2026-04-10T14:28:42.01Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/1e/354ed92461b165bd581f9ef5150971a572c873ec3b68a916d5aa91da3cc2/jiter-0.14.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:6f396837fc7577871ca8c12edaf239ed9ccef3bbe39904ae9b8b63ce0a48b140", size = 315277, upload-time = "2026-04-10T14:27:18.109Z" }, + { url = "https://files.pythonhosted.org/packages/a6/95/8c7c7028aa8636ac21b7a55faef3e34215e6ed0cbf5ae58258427f621aa3/jiter-0.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a4d50ea3d8ba4176f79754333bd35f1bbcd28e91adc13eb9b7ca91bc52a6cef9", size = 315923, upload-time = "2026-04-10T14:27:19.603Z" }, + { url = "https://files.pythonhosted.org/packages/47/40/e2a852a44c4a089f2681a16611b7ce113224a80fd8504c46d78491b47220/jiter-0.14.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce17f8a050447d1b4153bda4fb7d26e6a9e74eb4f4a41913f30934c5075bf615", size = 344943, upload-time = "2026-04-10T14:27:21.262Z" }, + { url = "https://files.pythonhosted.org/packages/fc/1f/670f92adee1e9895eac41e8a4d623b6da68c4d46249d8b556b60b63f949e/jiter-0.14.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f4f1c4b125e1652aefbc2e2c1617b60a160ab789d180e3d423c41439e5f32850", size = 369725, upload-time = "2026-04-10T14:27:22.766Z" }, + { url = "https://files.pythonhosted.org/packages/01/2f/541c9ba567d05de1c4874a0f8f8c5e3fd78e2b874266623da9a775cf46e0/jiter-0.14.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be808176a6a3a14321d18c603f2d40741858a7c4fc982f83232842689fe86dd9", size = 461210, upload-time = "2026-04-10T14:27:24.315Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a9/c31cbec09627e0d5de7aeaec7690dba03e090caa808fefd8133137cf45bc/jiter-0.14.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26679d58ba816f88c3849306dd58cb863a90a1cf352cdd4ef67e30ccf8a77994", size = 380002, upload-time = "2026-04-10T14:27:26.155Z" }, + { url = "https://files.pythonhosted.org/packages/50/02/3c05c1666c41904a2f607475a73e7a4763d1cbde2d18229c4f85b22dc253/jiter-0.14.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80381f5a19af8fa9aef743f080e34f6b25ebd89656475f8cf0470ec6157052aa", size = 354678, upload-time = "2026-04-10T14:27:27.701Z" }, + { url = "https://files.pythonhosted.org/packages/7d/97/e15b33545c2b13518f560d695f974b9891b311641bdcf178d63177e8801e/jiter-0.14.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:004df5fdb8ecbd6d99f3227df18ba1a259254c4359736a2e6f036c944e02d7c5", size = 358920, upload-time = "2026-04-10T14:27:29.256Z" }, + { url = "https://files.pythonhosted.org/packages/ad/d2/8b1461def6b96ba44530df20d07ef7a1c7da22f3f9bf1727e2d611077bf1/jiter-0.14.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cff5708f7ed0fa098f2b53446c6fa74c48469118e5cd7497b4f1cd569ab06928", size = 394512, upload-time = "2026-04-10T14:27:31.344Z" }, + { url = "https://files.pythonhosted.org/packages/e3/88/837566dd6ed6e452e8d3205355afd484ce44b2533edfa4ed73a298ea893e/jiter-0.14.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:2492e5f06c36a976d25c7cc347a60e26d5470178d44cde1b9b75e60b4e519f28", size = 521120, upload-time = "2026-04-10T14:27:33.299Z" }, + { url = "https://files.pythonhosted.org/packages/89/6b/b00b45c4d1b4c031777fe161d620b755b5b02cdade1e316dcb46e4471d63/jiter-0.14.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:7609cfbe3a03d37bfdbf5052012d5a879e72b83168a363deae7b3a26564d57de", size = 553668, upload-time = "2026-04-10T14:27:34.868Z" }, + { url = "https://files.pythonhosted.org/packages/ad/d8/6fe5b42011d19397433d345716eac16728ac241862a2aac9c91923c7509a/jiter-0.14.0-cp314-cp314-win32.whl", hash = "sha256:7282342d32e357543565286b6450378c3cd402eea333fc1ebe146f1fabb306fc", size = 207001, upload-time = "2026-04-10T14:27:36.455Z" }, + { url = "https://files.pythonhosted.org/packages/e5/43/5c2e08da1efad5e410f0eaaabeadd954812612c33fbbd8fd5328b489139d/jiter-0.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:bd77945f38866a448e73b0b7637366afa814d4617790ecd88a18ca74377e6c02", size = 202187, upload-time = "2026-04-10T14:27:38Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/6e39ac0b4cdfa23e606af5b245df5f9adaa76f35e0c5096790da430ca506/jiter-0.14.0-cp314-cp314-win_arm64.whl", hash = "sha256:f2d4c61da0821ee42e0cdf5489da60a6d074306313a377c2b35af464955a3611", size = 192257, upload-time = "2026-04-10T14:27:39.504Z" }, + { url = "https://files.pythonhosted.org/packages/05/57/7dbc0ffbbb5176a27e3518716608aa464aee2e2887dc938f0b900a120449/jiter-0.14.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1bf7ff85517dd2f20a5750081d2b75083c1b269cf75afc7511bdf1f9548beb3b", size = 323441, upload-time = "2026-04-10T14:27:41.039Z" }, + { url = "https://files.pythonhosted.org/packages/83/6e/7b3314398d8983f06b557aa21b670511ec72d3b79a68ee5e4d9bff972286/jiter-0.14.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8ef8791c3e78d6c6b157c6d360fbb5c715bebb8113bc6a9303c5caff012754a", size = 348109, upload-time = "2026-04-10T14:27:42.552Z" }, + { url = "https://files.pythonhosted.org/packages/ae/4f/8dc674bcd7db6dba566de73c08c763c337058baff1dbeb34567045b27cdc/jiter-0.14.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e74663b8b10da1fe0f4e4703fd7980d24ad17174b6bb35d8498d6e3ebce2ae6a", size = 368328, upload-time = "2026-04-10T14:27:44.574Z" }, + { url = "https://files.pythonhosted.org/packages/3b/5f/188e09a1f20906f98bbdec44ed820e19f4e8eb8aff88b9d1a5a497587ff3/jiter-0.14.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1aca29ba52913f78362ec9c2da62f22cdc4c3083313403f90c15460979b84d9b", size = 463301, upload-time = "2026-04-10T14:27:46.717Z" }, + { url = "https://files.pythonhosted.org/packages/ac/f0/19046ef965ed8f349e8554775bb12ff4352f443fbe12b95d31f575891256/jiter-0.14.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8b39b7d87a952b79949af5fef44d2544e58c21a28da7f1bae3ef166455c61746", size = 378891, upload-time = "2026-04-10T14:27:48.32Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c3/da43bd8431ee175695777ee78cf0e93eacbb47393ff493f18c45231b427d/jiter-0.14.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d918a68b26e9fab068c2b5453577ef04943ab2807b9a6275df2a812599a310", size = 360749, upload-time = "2026-04-10T14:27:49.88Z" }, + { url = "https://files.pythonhosted.org/packages/72/26/e054771be889707c6161dbdec9c23d33a9ec70945395d70f07cfea1e9a6f/jiter-0.14.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:b08997c35aee1201c1a5361466a8fb9162d03ae7bf6568df70b6c859f1e654a4", size = 358526, upload-time = "2026-04-10T14:27:51.504Z" }, + { url = "https://files.pythonhosted.org/packages/c3/0f/7bea65ea2a6d91f2bf989ff11a18136644392bf2b0497a1fa50934c30a9c/jiter-0.14.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:260bf7ca20704d58d41f669e5e9fe7fe2fa72901a6b324e79056f5d52e9c9be2", size = 393926, upload-time = "2026-04-10T14:27:53.368Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a1/b1ff7d70deef61ac0b7c6c2f12d2ace950cdeecb4fdc94500a0926802857/jiter-0.14.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:37826e3df29e60f30a382f9294348d0238ef127f4b5d7f5f8da78b5b9e050560", size = 521052, upload-time = "2026-04-10T14:27:55.058Z" }, + { url = "https://files.pythonhosted.org/packages/0b/7b/3b0649983cbaf15eda26a414b5b1982e910c67bd6f7b1b490f3cfc76896a/jiter-0.14.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:645be49c46f2900937ba0eaf871ad5183c96858c0af74b6becc7f4e367e36e06", size = 553716, upload-time = "2026-04-10T14:27:57.269Z" }, + { url = "https://files.pythonhosted.org/packages/97/f8/33d78c83bd93ae0c0af05293a6660f88a1977caef39a6d72a84afab94ce0/jiter-0.14.0-cp314-cp314t-win32.whl", hash = "sha256:2f7877ed45118de283786178eceaf877110abacd04fde31efff3940ae9672674", size = 207957, upload-time = "2026-04-10T14:27:59.285Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ac/2b760516c03e2227826d1f7025d89bf6bf6357a28fe75c2a2800873c50bf/jiter-0.14.0-cp314-cp314t-win_amd64.whl", hash = "sha256:14c0cb10337c49f5eafe8e7364daca5e29a020ea03580b8f8e6c597fed4e1588", size = 204690, upload-time = "2026-04-10T14:28:00.962Z" }, + { url = "https://files.pythonhosted.org/packages/dc/2e/a44c20c58aeed0355f2d326969a181696aeb551a25195f47563908a815be/jiter-0.14.0-cp314-cp314t-win_arm64.whl", hash = "sha256:5419d4aa2024961da9fe12a9cfe7484996735dca99e8e090b5c88595ef1951ff", size = 191338, upload-time = "2026-04-10T14:28:02.853Z" }, +] + +[[package]] +name = "macholib" +version = "1.16.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "altgraph" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/10/2f/97589876ea967487978071c9042518d28b958d87b17dceb7cdc1d881f963/macholib-1.16.4.tar.gz", hash = "sha256:f408c93ab2e995cd2c46e34fe328b130404be143469e41bc366c807448979362", size = 59427, upload-time = "2025-11-22T08:28:38.373Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/d1/a9f36f8ecdf0fb7c9b1e78c8d7af12b8c8754e74851ac7b94a8305540fc7/macholib-1.16.4-py2.py3-none-any.whl", hash = "sha256:da1a3fa8266e30f0ce7e97c6a54eefaae8edd1e5f86f3eb8b95457cae90265ea", size = 38117, upload-time = "2025-11-22T08:28:36.939Z" }, +] + +[[package]] +name = "openai" +version = "2.36.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/a1/4d5e84cf51720fc1526cc49e10ac1961abcccb55b0efb3d970db1e9a2728/openai-2.36.0.tar.gz", hash = "sha256:139dea0edd2f1b30c33d46ae1a6929e03906254140318e4608e98fe8c566f2e7", size = 753003, upload-time = "2026-05-07T17:33:17.075Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/1c/5d43735b2553baae2a5e899dcbcd0670a86930d993184d72ca909bf11c9b/openai-2.36.0-py3-none-any.whl", hash = "sha256:143f6194b548dbc2c921af1f1b03b9f14c85fed8a75b5b516f5bcc11a2a50c63", size = 1302361, upload-time = "2026-05-07T17:33:15.063Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "paramiko" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bcrypt" }, + { name = "cryptography" }, + { name = "invoke" }, + { name = "pynacl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1f/e7/81fdcbc7f190cdb058cffc9431587eb289833bdd633e2002455ca9bb13d4/paramiko-4.0.0.tar.gz", hash = "sha256:6a25f07b380cc9c9a88d2b920ad37167ac4667f8d9886ccebd8f90f654b5d69f", size = 1630743, upload-time = "2025-08-04T01:02:03.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/90/a744336f5af32c433bd09af7854599682a383b37cfd78f7de263de6ad6cb/paramiko-4.0.0-py3-none-any.whl", hash = "sha256:0e20e00ac666503bf0b4eda3b6d833465a2b7aff2e2b3d79a8bba5ef144ee3b9", size = 223932, upload-time = "2025-08-04T01:02:02.029Z" }, +] + +[[package]] +name = "pefile" +version = "2024.8.26" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/4f/2750f7f6f025a1507cd3b7218691671eecfd0bbebebe8b39aa0fe1d360b8/pefile-2024.8.26.tar.gz", hash = "sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632", size = 76008, upload-time = "2024-08-26T20:58:38.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/16/12b82f791c7f50ddec566873d5bdd245baa1491bac11d15ffb98aecc8f8b/pefile-2024.8.26-py3-none-any.whl", hash = "sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f", size = 74766, upload-time = "2024-08-26T21:01:02.632Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pydantic" +version = "2.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, + { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, + { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, + { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, + { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, + { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, + { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, + { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, + { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, + { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, + { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, + { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, +] + +[[package]] +name = "pyinstaller" +version = "6.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "altgraph" }, + { name = "macholib", marker = "sys_platform == 'darwin'" }, + { name = "packaging" }, + { name = "pefile", marker = "sys_platform == 'win32'" }, + { name = "pyinstaller-hooks-contrib" }, + { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/60/d03d52e6690d4e9caf333dcd14550cde634ce6c118b3bc8fa3112c3186fd/pyinstaller-6.20.0.tar.gz", hash = "sha256:95c5c7e03d5d61e9dfb8ef259c699cf492bb1041beb6dbe83696608cec07347a", size = 4048728, upload-time = "2026-04-22T20:59:36.96Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/e4/e228d6d1bbb7fd62dc660a8fb202a583b023d3a3624ca95d1a9290ee4d6a/pyinstaller-6.20.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:bf3be4e1284ee78ddccba5e29f99443a12a7b4673168288ffc4c9d38c6f7b90e", size = 1047642, upload-time = "2026-04-22T20:58:32.006Z" }, + { url = "https://files.pythonhosted.org/packages/ce/bd/afb631bcb3f9040efebd4f6d067f0828b51710818f69fb41a2d4b7787f52/pyinstaller-6.20.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:72ae9c1fdea134afa791f58bdc9a1934d5c7609753c111e0026bfc272b32b712", size = 742494, upload-time = "2026-04-22T20:58:36.285Z" }, + { url = "https://files.pythonhosted.org/packages/76/08/0729a5bac14754150e5d83b39d87d842eb42b0bffcaa03dbad6252e23a39/pyinstaller-6.20.0-py3-none-manylinux2014_i686.whl", hash = "sha256:1031bcc307f3fbeffd4e162723e64d46dbf591c82dd0997413afb2a07328b941", size = 754191, upload-time = "2026-04-22T20:58:40.603Z" }, + { url = "https://files.pythonhosted.org/packages/e6/82/bc0ee4c7b97db1958eb651e0da9fb1e672e5ae53ca8867fd97701de52906/pyinstaller-6.20.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:8df3b3f347659fa2562d8d193a98ad4600133b8b8d07c268df89e4154376750e", size = 751902, upload-time = "2026-04-22T20:58:44.7Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e7/770002d6aaa54173881cb2c49bb195ba67b97bf39bac1cdf320f28401629/pyinstaller-6.20.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:b0d3cc9dd8120d448459bd3880a12e2f9774c51443af49047801446377999a59", size = 748634, upload-time = "2026-04-22T20:58:48.579Z" }, + { url = "https://files.pythonhosted.org/packages/fe/db/68ba1fccb71278b2124fb90b37b7c8c0bc4c1173fba45b94466df3d9cb7f/pyinstaller-6.20.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:03696bb6350177c6bc23bcaf78e71a33c4a89b6754dd90d1be2f318e978c918b", size = 748490, upload-time = "2026-04-22T20:58:52.749Z" }, + { url = "https://files.pythonhosted.org/packages/03/0f/ac77ffa996a56be3d5c8f85734a007f8347240691657f9704e7de2527fa3/pyinstaller-6.20.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:6357f1699f6af84f37e7367f031d4f68abdba65543b83990c9e8f5a4cebed0b7", size = 747650, upload-time = "2026-04-22T20:58:57.093Z" }, + { url = "https://files.pythonhosted.org/packages/e0/56/1ee91c3a2bc10ca1f36da10a6fd55ff7efc4dec367171eb25992a827874f/pyinstaller-6.20.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:0ab39c690abad26ba148e8f664f0478acc82a733997f4f22e757774832802da9", size = 747413, upload-time = "2026-04-22T20:59:01.174Z" }, + { url = "https://files.pythonhosted.org/packages/d7/55/ae264339996953c4cdf9d89d916a0a8fa26a83cf917a742fff8b9d5f3fe8/pyinstaller-6.20.0-py3-none-win32.whl", hash = "sha256:9a7637e8e44b4387b13667fdcaac86ab6b29c446c16d34d8401539b81838759c", size = 1331584, upload-time = "2026-04-22T20:59:07.201Z" }, + { url = "https://files.pythonhosted.org/packages/76/8c/300f57578882cce259bfb5ae56fda3b69caa3fe9df40a176c719920ea6e2/pyinstaller-6.20.0-py3-none-win_amd64.whl", hash = "sha256:d588844e890ee80c4365867f98146636e1849bbca8e4284bbf0c809aff0f161a", size = 1391851, upload-time = "2026-04-22T20:59:14.024Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ea/b2f8e1642aecda78c0b75c7321f708e49e10bb3c00dd4f148c40761a1527/pyinstaller-6.20.0-py3-none-win_arm64.whl", hash = "sha256:bd53282c0a73e5c95573e1ddc8e5d564d4932bec91efbaed4dc5fdff9c2ae7f2", size = 1332259, upload-time = "2026-04-22T20:59:20.509Z" }, +] + +[[package]] +name = "pyinstaller-hooks-contrib" +version = "2026.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/67/f4452d68793fb15beba4f19ef39a38a8822f0da7452b503c400d5a21f5c1/pyinstaller_hooks_contrib-2026.5.tar.gz", hash = "sha256:f066dfca8f7c45ff6336c9cf9fe25b4e48bfeb322a1aa24faaedfb8a8d1b0b08", size = 173689, upload-time = "2026-05-04T22:36:55.124Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/5c/fd465d11da4d12b50d7eb5d2ee2ceb780d8d049dbb489f3828d131e387af/pyinstaller_hooks_contrib-2026.5-py3-none-any.whl", hash = "sha256:ea1535783fbdac4626351709e83f3ea80b681d3a4745763ebb407b5e27342eb9", size = 457314, upload-time = "2026-05-04T22:36:53.598Z" }, +] + +[[package]] +name = "pynacl" +version = "1.6.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/9a/4019b524b03a13438637b11538c82781a5eda427394380381af8f04f467a/pynacl-1.6.2.tar.gz", hash = "sha256:018494d6d696ae03c7e656e5e74cdfd8ea1326962cc401bcf018f1ed8436811c", size = 3511692, upload-time = "2026-01-01T17:48:10.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/79/0e3c34dc3c4671f67d251c07aa8eb100916f250ee470df230b0ab89551b4/pynacl-1.6.2-cp314-cp314t-macosx_10_10_universal2.whl", hash = "sha256:622d7b07cc5c02c666795792931b50c91f3ce3c2649762efb1ef0d5684c81594", size = 390064, upload-time = "2026-01-01T17:31:57.264Z" }, + { url = "https://files.pythonhosted.org/packages/eb/1c/23a26e931736e13b16483795c8a6b2f641bf6a3d5238c22b070a5112722c/pynacl-1.6.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d071c6a9a4c94d79eb665db4ce5cedc537faf74f2355e4d502591d850d3913c0", size = 809370, upload-time = "2026-01-01T17:31:59.198Z" }, + { url = "https://files.pythonhosted.org/packages/87/74/8d4b718f8a22aea9e8dcc8b95deb76d4aae380e2f5b570cc70b5fd0a852d/pynacl-1.6.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe9847ca47d287af41e82be1dd5e23023d3c31a951da134121ab02e42ac218c9", size = 1408304, upload-time = "2026-01-01T17:32:01.162Z" }, + { url = "https://files.pythonhosted.org/packages/fd/73/be4fdd3a6a87fe8a4553380c2b47fbd1f7f58292eb820902f5c8ac7de7b0/pynacl-1.6.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:04316d1fc625d860b6c162fff704eb8426b1a8bcd3abacea11142cbd99a6b574", size = 844871, upload-time = "2026-01-01T17:32:02.824Z" }, + { url = "https://files.pythonhosted.org/packages/55/ad/6efc57ab75ee4422e96b5f2697d51bbcf6cdcc091e66310df91fbdc144a8/pynacl-1.6.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44081faff368d6c5553ccf55322ef2819abb40e25afaec7e740f159f74813634", size = 1446356, upload-time = "2026-01-01T17:32:04.452Z" }, + { url = "https://files.pythonhosted.org/packages/78/b7/928ee9c4779caa0a915844311ab9fb5f99585621c5d6e4574538a17dca07/pynacl-1.6.2-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:a9f9932d8d2811ce1a8ffa79dcbdf3970e7355b5c8eb0c1a881a57e7f7d96e88", size = 826814, upload-time = "2026-01-01T17:32:06.078Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a9/1bdba746a2be20f8809fee75c10e3159d75864ef69c6b0dd168fc60e485d/pynacl-1.6.2-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:bc4a36b28dd72fb4845e5d8f9760610588a96d5a51f01d84d8c6ff9849968c14", size = 1411742, upload-time = "2026-01-01T17:32:07.651Z" }, + { url = "https://files.pythonhosted.org/packages/f3/2f/5e7ea8d85f9f3ea5b6b87db1d8388daa3587eed181bdeb0306816fdbbe79/pynacl-1.6.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bffb6d0f6becacb6526f8f42adfb5efb26337056ee0831fb9a7044d1a964444", size = 801714, upload-time = "2026-01-01T17:32:09.558Z" }, + { url = "https://files.pythonhosted.org/packages/06/ea/43fe2f7eab5f200e40fb10d305bf6f87ea31b3bbc83443eac37cd34a9e1e/pynacl-1.6.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2fef529ef3ee487ad8113d287a593fa26f48ee3620d92ecc6f1d09ea38e0709b", size = 1372257, upload-time = "2026-01-01T17:32:11.026Z" }, + { url = "https://files.pythonhosted.org/packages/4d/54/c9ea116412788629b1347e415f72195c25eb2f3809b2d3e7b25f5c79f13a/pynacl-1.6.2-cp314-cp314t-win32.whl", hash = "sha256:a84bf1c20339d06dc0c85d9aea9637a24f718f375d861b2668b2f9f96fa51145", size = 231319, upload-time = "2026-01-01T17:32:12.46Z" }, + { url = "https://files.pythonhosted.org/packages/ce/04/64e9d76646abac2dccf904fccba352a86e7d172647557f35b9fe2a5ee4a1/pynacl-1.6.2-cp314-cp314t-win_amd64.whl", hash = "sha256:320ef68a41c87547c91a8b58903c9caa641ab01e8512ce291085b5fe2fcb7590", size = 244044, upload-time = "2026-01-01T17:32:13.781Z" }, + { url = "https://files.pythonhosted.org/packages/33/33/7873dc161c6a06f43cda13dec67b6fe152cb2f982581151956fa5e5cdb47/pynacl-1.6.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d29bfe37e20e015a7d8b23cfc8bd6aa7909c92a1b8f41ee416bbb3e79ef182b2", size = 188740, upload-time = "2026-01-01T17:32:15.083Z" }, + { url = "https://files.pythonhosted.org/packages/be/7b/4845bbf88e94586ec47a432da4e9107e3fc3ce37eb412b1398630a37f7dd/pynacl-1.6.2-cp38-abi3-macosx_10_10_universal2.whl", hash = "sha256:c949ea47e4206af7c8f604b8278093b674f7c79ed0d4719cc836902bf4517465", size = 388458, upload-time = "2026-01-01T17:32:16.829Z" }, + { url = "https://files.pythonhosted.org/packages/1e/b4/e927e0653ba63b02a4ca5b4d852a8d1d678afbf69b3dbf9c4d0785ac905c/pynacl-1.6.2-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8845c0631c0be43abdd865511c41eab235e0be69c81dc66a50911594198679b0", size = 800020, upload-time = "2026-01-01T17:32:18.34Z" }, + { url = "https://files.pythonhosted.org/packages/7f/81/d60984052df5c97b1d24365bc1e30024379b42c4edcd79d2436b1b9806f2/pynacl-1.6.2-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22de65bb9010a725b0dac248f353bb072969c94fa8d6b1f34b87d7953cf7bbe4", size = 1399174, upload-time = "2026-01-01T17:32:20.239Z" }, + { url = "https://files.pythonhosted.org/packages/68/f7/322f2f9915c4ef27d140101dd0ed26b479f7e6f5f183590fd32dfc48c4d3/pynacl-1.6.2-cp38-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:46065496ab748469cdd999246d17e301b2c24ae2fdf739132e580a0e94c94a87", size = 835085, upload-time = "2026-01-01T17:32:22.24Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d0/f301f83ac8dbe53442c5a43f6a39016f94f754d7a9815a875b65e218a307/pynacl-1.6.2-cp38-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a66d6fb6ae7661c58995f9c6435bda2b1e68b54b598a6a10247bfcdadac996c", size = 1437614, upload-time = "2026-01-01T17:32:23.766Z" }, + { url = "https://files.pythonhosted.org/packages/c4/58/fc6e649762b029315325ace1a8c6be66125e42f67416d3dbd47b69563d61/pynacl-1.6.2-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:26bfcd00dcf2cf160f122186af731ae30ab120c18e8375684ec2670dccd28130", size = 818251, upload-time = "2026-01-01T17:32:25.69Z" }, + { url = "https://files.pythonhosted.org/packages/c9/a8/b917096b1accc9acd878819a49d3d84875731a41eb665f6ebc826b1af99e/pynacl-1.6.2-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c8a231e36ec2cab018c4ad4358c386e36eede0319a0c41fed24f840b1dac59f6", size = 1402859, upload-time = "2026-01-01T17:32:27.215Z" }, + { url = "https://files.pythonhosted.org/packages/85/42/fe60b5f4473e12c72f977548e4028156f4d340b884c635ec6b063fe7e9a5/pynacl-1.6.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:68be3a09455743ff9505491220b64440ced8973fe930f270c8e07ccfa25b1f9e", size = 791926, upload-time = "2026-01-01T17:32:29.314Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f9/e40e318c604259301cc091a2a63f237d9e7b424c4851cafaea4ea7c4834e/pynacl-1.6.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8b097553b380236d51ed11356c953bf8ce36a29a3e596e934ecabe76c985a577", size = 1363101, upload-time = "2026-01-01T17:32:31.263Z" }, + { url = "https://files.pythonhosted.org/packages/48/47/e761c254f410c023a469284a9bc210933e18588ca87706ae93002c05114c/pynacl-1.6.2-cp38-abi3-win32.whl", hash = "sha256:5811c72b473b2f38f7e2a3dc4f8642e3a3e9b5e7317266e4ced1fba85cae41aa", size = 227421, upload-time = "2026-01-01T17:32:33.076Z" }, + { url = "https://files.pythonhosted.org/packages/41/ad/334600e8cacc7d86587fe5f565480fde569dfb487389c8e1be56ac21d8ac/pynacl-1.6.2-cp38-abi3-win_amd64.whl", hash = "sha256:62985f233210dee6548c223301b6c25440852e13d59a8b81490203c3227c5ba0", size = 239754, upload-time = "2026-01-01T17:32:34.557Z" }, + { url = "https://files.pythonhosted.org/packages/29/7d/5945b5af29534641820d3bd7b00962abbbdfee84ec7e19f0d5b3175f9a31/pynacl-1.6.2-cp38-abi3-win_arm64.whl", hash = "sha256:834a43af110f743a754448463e8fd61259cd4ab5bbedcf70f9dabad1d28a394c", size = 184801, upload-time = "2026-01-01T17:32:36.309Z" }, +] + +[[package]] +name = "pyside6" +version = "6.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyside6-addons" }, + { name = "pyside6-essentials" }, + { name = "shiboken6" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/95/f3f5a2799163b6658126d78a85bc1dec9eda88c75c26780556b26071a1d8/pyside6-6.11.0-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:1f2735dc4f2bd4ec452ae50502c8a22128bba0aced35358a2bbc58384b820c6f", size = 571544, upload-time = "2026-03-23T12:47:20.263Z" }, + { url = "https://files.pythonhosted.org/packages/da/89/9a1f521051714e6694ebbe2b979ded279845ec8e25cb309ca3960158d74f/pyside6-6.11.0-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c642e2d25704ca746fd37f56feacf25c5aecc4cd40bef23d18eec81f87d9dc00", size = 571725, upload-time = "2026-03-23T12:47:21.727Z" }, + { url = "https://files.pythonhosted.org/packages/c2/3d/f779d8bba00fcde31a7d7fb6b59347a70773c9cc8135592dea9972579877/pyside6-6.11.0-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:267b344c73580ac938ca63c611881fb42a3922ebfe043e271005f4f06c372c4e", size = 571722, upload-time = "2026-03-23T12:47:22.761Z" }, + { url = "https://files.pythonhosted.org/packages/ac/98/150e01a026df3e9697310236821fa825319bb4b9d6137539cb25a3032968/pyside6-6.11.0-cp310-abi3-win_amd64.whl", hash = "sha256:9092cb002ca43c64006afb2e0d0f6f51aef17aa737c33a45e502326a081ddcbc", size = 577988, upload-time = "2026-03-23T12:47:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/50/e7/55960f7c6b41d058e95cb4af02652c46c48702c506c8bbf12e99550e1fb3/pyside6-6.11.0-cp310-abi3-win_arm64.whl", hash = "sha256:b15f39acc2b8f46251a630acad0d97f9a0a0461f2baffcd66d7adfada8eb641e", size = 561372, upload-time = "2026-03-23T12:47:25.073Z" }, +] + +[[package]] +name = "pyside6-addons" +version = "6.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyside6-essentials" }, + { name = "shiboken6" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/df/241f311c61a46b7b1195927da77b2537692ee3442aa9ccd87981164ff78d/pyside6_addons-6.11.0-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:d5eaa4643302e3a0fa94c5766234bee4073d7d5ab9c2b7fd222692a176faf182", size = 331554157, upload-time = "2026-03-23T12:40:40.497Z" }, + { url = "https://files.pythonhosted.org/packages/31/b9/e81172835ccc9d8b9792cc6bf7524a252a0db9a76ddd693de230402697f9/pyside6_addons-6.11.0-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ac6fe3d4ef4497dde3efc5e896b0acd53ff6c93be4bf485f045690f919419f35", size = 174948482, upload-time = "2026-03-23T12:41:05.379Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a4/426d9333782bf65ab2a20257d6b4b3af9b8d5d7a710da719865fab49d492/pyside6_addons-6.11.0-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:8ffb40222456078930816ebcac2f2511716d2acbc11716dd5acc5c365179a753", size = 170430798, upload-time = "2026-03-23T12:41:38.134Z" }, + { url = "https://files.pythonhosted.org/packages/35/9a/46d271fedfabad8c6dce2ebb69bb593745487ed33753a56a47c3ba4fdb1c/pyside6_addons-6.11.0-cp310-abi3-win_amd64.whl", hash = "sha256:413e6121c24f5ffdce376298059eddecff74aa6d638e94e0f6015b33d29b889e", size = 168723088, upload-time = "2026-03-23T12:42:00.668Z" }, + { url = "https://files.pythonhosted.org/packages/16/cd/1b28264f7dc9a642da2e4e7c02f67418d0949eb7ce329ae20869703c2630/pyside6_addons-6.11.0-cp310-abi3-win_arm64.whl", hash = "sha256:aaaee83385977a0fe134b2f4fbfb92b45a880d5b656e4d90a708eef10b1b6de8", size = 35698324, upload-time = "2026-03-23T12:42:13.748Z" }, +] + +[[package]] +name = "pyside6-essentials" +version = "6.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "shiboken6" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/00/8a8583d3429c737cc20e61a43eba8ab1ec13ddb101e99802c2ffeedf3b41/pyside6_essentials-6.11.0-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:85d6ca87ef35fa6565d385ede72ae48420dd3f63113929d10fc800f6b0360e01", size = 108085251, upload-time = "2026-03-23T12:42:52.872Z" }, + { url = "https://files.pythonhosted.org/packages/f3/a9/07c9e5c014b871c1b19caf8f994bcd50b345559b81f81671217b49559b67/pyside6_essentials-6.11.0-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:dc20e7afd5fc6fe51297db91cef997ce60844be578f7a49fc61b7ab9657a8849", size = 78316055, upload-time = "2026-03-23T12:43:04.19Z" }, + { url = "https://files.pythonhosted.org/packages/7c/35/f06b1b641d7600ec46374c16cd37c66fa4a22870326b4eb073a95471035f/pyside6_essentials-6.11.0-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:4854cb0a1b061e7a576d8fb7bb7cf9f49540d558b1acb7df0742a7afefe61e4e", size = 77380821, upload-time = "2026-03-23T12:43:24.649Z" }, + { url = "https://files.pythonhosted.org/packages/ff/37/ba95c6262836d2b286b4e05a9d16a5e870995d5d2503ac6adc6312208049/pyside6_essentials-6.11.0-cp310-abi3-win_amd64.whl", hash = "sha256:3b3362882ad9389357a80504e600180006a957731fec05786fced7b038461fdf", size = 75793322, upload-time = "2026-03-23T12:43:35.575Z" }, + { url = "https://files.pythonhosted.org/packages/53/27/d17f25e45820e633a70e6109b35991eda09a5e8000c2a306f0ab7538d48c/pyside6_essentials-6.11.0-cp310-abi3-win_arm64.whl", hash = "sha256:81ca603dbf21bc39f89bb42db215c25ebe0c879a1a4c387625c321d2730ec187", size = 56337457, upload-time = "2026-03-23T12:43:43.573Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, +] + +[[package]] +name = "pywin32" +version = "311" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, +] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, +] + +[[package]] +name = "setuptools" +version = "82.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" }, +] + +[[package]] +name = "shiboken6" +version = "6.11.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/1d/b56b7b694fbc871496435488d1f41c5068de546334850d722756511cef65/shiboken6-6.11.0-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:d88e8a1eb705f2b9ad21db08a61ae1dc0c773e5cd86a069de0754c4cf1f9b43b", size = 476085, upload-time = "2026-03-23T12:47:05.724Z" }, + { url = "https://files.pythonhosted.org/packages/65/cb/4bb0c76011166230daa7c0074aeb3fdb3935c83ac1fef3789b85fcd1a8fc/shiboken6-6.11.0-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ad54e64f8192ddbdff0c54ac82b89edcd62ed623f502ea21c960541d19514053", size = 271055, upload-time = "2026-03-23T12:47:07.349Z" }, + { url = "https://files.pythonhosted.org/packages/f5/96/771a6e2b530f725303d16d78a321fa4876b98b4f3615c9851880df8c1a43/shiboken6-6.11.0-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:a10dc7718104ea2dc15d5b0b96909b77162ce1c76fcc6968e6df692b947a00e9", size = 267456, upload-time = "2026-03-23T12:47:08.689Z" }, + { url = "https://files.pythonhosted.org/packages/72/f7/44c0c42c3f5f29dec457fd46ea0552174bcb8aa75becf03bbd90308ba07b/shiboken6-6.11.0-cp310-abi3-win_amd64.whl", hash = "sha256:483ff78a73c7b3189ca924abc694318084f078bcfeaffa68e32024ff2d025ee1", size = 1222132, upload-time = "2026-03-23T12:47:10.143Z" }, + { url = "https://files.pythonhosted.org/packages/fb/99/6e5ee21db2d6af84bbbd7d871d441dafeb069c6de5667b1aa49891a77c66/shiboken6-6.11.0-cp310-abi3-win_arm64.whl", hash = "sha256:3bd76cf56105ab2d62ecaff630366f11264f69b88d488f10f048da9a065781f4", size = 1783186, upload-time = "2026-03-23T12:47:11.832Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "virtualbox-client" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "openai" }, + { name = "paramiko" }, + { name = "pyside6" }, + { name = "python-dotenv" }, + { name = "pywin32" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pyinstaller" }, + { name = "setuptools" }, +] + +[package.metadata] +requires-dist = [ + { name = "openai", specifier = ">=2.35.1" }, + { name = "paramiko", specifier = ">=4.0.0" }, + { name = "pyside6", specifier = ">=6.8.0" }, + { name = "python-dotenv", specifier = ">=1.2.2" }, + { name = "pywin32", specifier = ">=311" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "pyinstaller", specifier = ">=6.20.0" }, + { name = "setuptools", specifier = ">=82.0.1" }, +]