Metadata-Version: 2.4
Name: ssh-agent-harness
Version: 0.1.0rc1
Summary: SSH Agent Harness using local Ollama (deepseek-r1) for command generation/execution
Author: SSH Agent Harness Contributors
License-Expression: MIT
Keywords: ssh,agent,ollama,llm,automation,devops
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: System :: Systems Administration
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.12
Description-Content-Type: text/markdown
Requires-Dist: asyncssh>=2.14
Requires-Dist: fastapi>=0.111
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.7
Requires-Dist: pydantic-settings>=2.3
Requires-Dist: pyyaml>=6.0
Requires-Dist: requests>=2.31
Requires-Dist: typer>=0.12
Requires-Dist: rich>=13.7
Requires-Dist: sqlite-utils>=3.37
Requires-Dist: tenacity>=8.2
Requires-Dist: structlog>=24.1
Requires-Dist: uvicorn[standard]>=0.30
Provides-Extra: dev
Requires-Dist: ruff>=0.4; extra == "dev"
Requires-Dist: mypy>=1.10; extra == "dev"
Requires-Dist: pytest>=8.2; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: pytest-cov>=5.0; extra == "dev"
Requires-Dist: pre-commit>=3.7; extra == "dev"
Requires-Dist: bandit>=1.7; extra == "dev"
Provides-Extra: test
Requires-Dist: pytest>=8.2; extra == "test"
Requires-Dist: pytest-asyncio>=0.23; extra == "test"
Requires-Dist: pytest-cov>=5.0; extra == "test"
Requires-Dist: fakeredis>=2.21; extra == "test"

# SSH Agent Harness

> LLM 驱动的 SSH 命令生成/执行框架，**仅本机 Web 控制台**。
> 支持本机 Ollama（deepseek-r1）或任意 OpenAI 兼容端点（DeepSeek/vLLM/Azure），
> 以 Harness 架构把「模型 + 工具 + 上下文 + 评估 + 护栏」组合成安全可审计的运维助手。

## 架构

```
┌─────────────────────────────────────────────────────────────┐
│                 SSH Agent Harness（纯 Web）                 │
├─────────────────────────────────────────────────────────────┤
│  Web 控制台（FastAPI + React SPA）                          │
├─────────────────────────────────────────────────────────────┤
│  Generator (deepseek-r1)   │  命令生成 + CoT / 多轮修复     │
├─────────────────────────────────────────────────────────────┤
│  Tool Execution Layer      │  SSH / SFTP 传输 / 文件        │
├─────────────────────────────────────────────────────────────┤
│  Context & Memory          │  会话 / 服务器工作空间 / 指纹    │
├─────────────────────────────────────────────────────────────┤
│  Evaluator / Guardrails    │  安全分级 / 审批门控 / 审计    │
└─────────────────────────────────────────────────────────────┘
```

## 安装

从 PyPI 安装（**自带前端控制台**，装完直接可用）：

```bash
pip install ssh-agent-harness
ssh-agent-web            # 浏览器打开 http://127.0.0.1:8000
```

开发（源码 + 热更新）：

```bash
conda activate python312
pip install -e ".[dev]"
```

打包与发布（本地构建、TestPyPI、正式 PyPI）见 [`docs/packaging-and-release.md`](docs/packaging-and-release.md)。

也可使用本机模型：先 `ollama serve`，再 `ollama pull deepseek-r1:1.5b`；
或通过环境变量指向外部 OpenAI 兼容端点（key 只走 env，勿入库）：

```bash
export LLM_PROVIDER=openai LLM_MODEL=deepseek-chat LLM_API_KEY=***
# 可选：LLM_BASE_URL=https://api.deepseek.com
```

## 启动

**唯一入口是 Web 控制台 `ssh-agent-web`**（`ssh_agent_harness/api/__main__.py:main`）：

```bash
ssh-agent-web --db ./data/agent.db
# 或显式参数：
ssh-agent-web --host 127.0.0.1 --port 8000 --db ./data/agent.db --approval-timeout 900
```

> ⚠️ **只能用 `ssh-agent-web` 启动**。不要用 `uvicorn` 直启 app
> （`python -m uvicorn ssh_agent_harness.api.app:app`）——它不解析 `--db`，
> 会落到用户默认库并触发启动清理（误伤在飞任务）。

| 参数 | 默认值 | 说明 |
| --- | --- | --- |
| `--host` | `127.0.0.1` | 绑定地址；默认只监听回环 |
| `--port` | `8000` | 监听端口 |
| `--db` | `settings.storage.db_path` | SQLite 路径 |
| `--approval-timeout` | `900.0` | 审批等待秒数；超时后审批置 expired、任务判 denied |
| `--static` | 包内自带的 SPA | 覆盖内置前端目录；源码开发时指向 `web/dist`，安装版无需传 |

交互式 API 文档：`http://127.0.0.1:<port>/docs`。

日志：启动时按 `settings.yaml` 的 `logging` 段装配（控制台 + 轮转文件，默认
`~/.ssh-agent-harness/ssh-agent.log`，`format: json` 每行一条；`~` 自动展开）。

### 前端 dev / 构建

前端控制台源码位于 `web/`（React 18 + TypeScript + Vite 5 + Ant Design 5 + react-query 5）：

```bash
# dev 双进程：只起本后端（纯 API），前端 dev server 另行启动并把 /api 反代到后端
ssh-agent-web --port 8000
npm run dev --prefix web        # vite dev server（5173），/api 反代到 127.0.0.1:8000

# build 单进程：前端产出 dist，后端同一进程托管 SPA 与 /api
npm run build --prefix web      # 产出 web/dist（不入库）
ssh-agent-web --port 8000 --static web/dist
```

> 后端未开 CORS，dev 模式请走 vite 的 `/api` 反向代理，勿让前端跨源直连。

## 配置

- `config/settings.yaml`：主设置（LLM provider、SSH、safety、storage/SQLite 路径）。
- 服务器清单存 SQLite `hosts` 表（经 Web 控制台「服务器管理」页增删改；DB 密码明文=已知接受风险，API 永不回显 password）。
- LLM provider 生效优先级：**UI 控制台配置 > env `LLM_*` > `settings.yaml`**。
  - UI：「模型配置」可切 Ollama / DeepSeek API / 自定义端点，选择存 SQLite（密钥明文为已知风险、响应永不回显）。
  - env：`LLM_PROVIDER` / `LLM_MODEL` / `LLM_BASE_URL` / `LLM_API_KEY`（或 `DEEPSEEK_API_KEY`）。
  - `LLM_TIMEOUT`（env）可覆盖读超时（默认 600s）。
  - 注意 `config.py` 以 YAML init 注入 Settings，`OLLAMA__*` 风格 env 覆盖**不生效**，请用 `LLM_*`。
- Safety：`rm -rf` 类黑名单为**硬 DENY**（审批不可越过）；`systemctl stop` 等触发确认门；`kill/pkill` 默认直接放行。

## Web 功能

- **提交任务**：智能模式（默认）/ 快速模式，可设 dry-run、修复轮数（rounds）、步进循环。
- **审批弹窗**：危险命令挂起，弹窗内批准/拒绝；超时自动拒绝。
- **服务器管理**：YAML ∪ DB 合并视图，CRUD + 连通性测试。
- **服务器工作空间（知识库）**：按服务器维护规范与事实，供探测轮注入。
- **任务记录**：执行记录（sessions）与审计轨迹、run 详情时间线、按 run 撤销工作空间事实。

### run 状态机

`queued → generating → [awaiting_approval ⇄ running] → succeeded | failed | denied | cancelled`
（dry-run 不经审批；cancel 优先于并发完成；重启时未终结的 run 在启动清理中置 failed）

### 多轮修复（rounds）

`POST /api/runs` 请求体 `rounds`（1-10，缺省 1）大于 1 时启用反馈回路：每轮执行完毕，
把命令及其 stdout/stderr 组成观察回灌给模型，模型判定任务是否完成——输出 `TASK_COMPLETE`
是确认成功的唯一通道；未完成则给出修正命令，下一轮继续执行；轮尽仍未确认 → 终态 `failed`
（verdict=`exhausted`）。dry-run 同样适用。verdict 词表
`complete|exhausted|no-progress|repair-failed`，`null` = 终态未落判定。

### 安全边界

- **默认仅监听 127.0.0.1，且无任何鉴权**：能访问该端口即可完整操作服务器，勿暴露到公网。
- **明文密码为已知风险**：DB 服务器的密码明文存于 SQLite，且 `PUT /api/hosts` 请求体明文传输（仅回环缓解）。
- **cancel 不打断在飞命令**：只阻止后续命令执行并落终态 `cancelled`，已发出的 SSH 命令会跑完。
- **安全黑名单硬 DENY**：`rm -rf /`、`mkfs`、`dd of=/dev/*` 等被 gate 直接拒绝，审批无法越过。
- **审批超时即 denied**：等待 `--approval-timeout` 秒无人决策 → 审批 expired、任务 denied。
- **同一数据库文件同时只允许运行一个 `ssh-agent-web` 实例**：启动清理会把库内全部非终态 run
  置 failed；多实例请各自指定独立 `--db`。
- **dry-run 不落库**：只生成不执行，也不写 session 历史。
- 服务器/审批响应一律脱敏：不回显密码与私钥内容。

### API 速览

| 方法与路径 | 说明 |
| --- | --- |
| `GET /api/version` | 版本号 |
| `GET /api/llm` | 当前生效模型 + provider 目录（presets）+ `api_key_set`；永不回显密钥 |
| `PUT /api/llm` `{provider, model, api_key?, adapter_type?, base_url?}` | 切换模型（UI 配置存 SQLite，优先于 env/settings） |
| `DELETE /api/llm` | 清除 UI 切换，回落 env/settings |
| `GET /api/hosts` | 服务器清单（读 SQLite `hosts` 表，脱敏） |
| `POST /api/hosts` | 新建 DB 服务器（同名 shadow YAML 服务器，DB 字段优先） |
| `PUT /api/hosts/{name}` | 更新 DB 服务器；YAML 服务器 403 |
| `DELETE /api/hosts/{name}` | 删除 DB 服务器行；YAML 服务器 403 |
| `POST /api/hosts/{name}/test` | 连通性探测，返回 `{reachable, latency_ms, error}` |
| `GET /api/sessions?host=&status=&limit=&offset=` | 执行历史，新→旧 |
| `GET /api/sessions/{id}` | 会话详情 + 按时间正序的审计轨迹 |
| `DELETE /api/sessions/{id}` | 删除会话及其审计 |
| `POST /api/runs` `{task, host, dry_run?, rounds?, step?}` | 提交后台任务 → 202 |
| `GET /api/runs?limit=&offset=` | run 列表（新→旧，全状态） |
| `GET /api/runs/{id}` | 轮询：status / current_step / commands / results |
| `POST /api/runs/{id}/cancel` | 取消活跃 run；终态或未知 → 409 |
| `GET /api/approvals?run_id=&status=pending` | 待审批收件箱（仅支持 pending） |
| `POST /api/approvals/{id}/decide` `{decision: approve\|deny}` | 首次决策生效并唤醒任务；重复 decide 409 |
| `GET/PUT/DELETE /api/knowledge/...` | 服务器工作空间（规范与事实）读写与撤销 |

## 测试与门禁

```bash
# 后端单测（无需 Ollama/SSH）
conda run -n python312 --no-capture-output python -m pytest -m "not integration" -q

# 类型检查（strict）
mypy ssh_agent_harness

# 前端门禁
npm run typecheck --prefix web
npm run build --prefix web
```

提交门槛 = 上述四项全绿。集成测试（需 Ollama + 数据库中配置的真实服务器）：
`conda run -n python312 --no-capture-output python -m pytest -m integration -v`。

## 目录结构（纯 Web）

```
ssh_agent_harness/
├── api/            # Web 后端 FastAPI 包（app/run_manager/routes/schemas/deps/__main__）
├── core/           # orchestrator/step_loop/multi_turn/llm 工厂/host_loader/run_persistence/...
├── tools/          # ssh_pool(asyncssh)/paramiko fallback/command_executor
├── harness/        # safety + confirmation(含 Web 挂起审批) + evaluator/rollback/protocol
├── storage/        # session_store(SQLite) + run_store(runs/approvals) + host_store
└── config.py       # pydantic-settings Settings
web/                # React18+TS+Vite+AntD5 控制台（dist 不入库；打包前由 scripts/build_frontend.py 拷入包内 static/）
tests/              # 最小 Web 测试集（api smoke / web entry / config）
docs/superpowers/   # 黄金路径手册等
config/             # settings.yaml（服务器在 DB，经 Web 服务器管理页维护）
```

## License

MIT
