diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..b8ca34f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,271 @@ +# AGENTS.md + +## Purpose + +This repo is a small Python Flask service that converts subscription links (`vmess`, `vless`, `trojan`, `ss`) into local SOCKS5 proxies via `xray-core`. +Almost all backend logic, API routes, and the inline web UI live in `main.py`. +Agents should prefer minimal, in-place changes over architectural rewrites. + +## Repository Layout + +- `main.py`: app entrypoint, dataclasses, parsers, proxy manager, schedulers, Flask routes, and embedded HTML/CSS/JS. +- `requirements.txt`: runtime dependencies. +- `start.sh`: local bootstrap script. +- `Dockerfile`: image build. +- `docker-compose.yml`: recommended container run path. +- `config.yaml.example`: optional config sample. +- `config/`: persisted JSON state. +- `data/`: runtime files such as generated xray configs. +- `bin/`: xray binary/assets. + +## Extra Agent Rules + +I checked for repo-specific agent instruction files: + +- `.cursor/rules/`: not present. +- `.cursorrules`: not present. +- `.github/copilot-instructions.md`: not present. + +There are no Cursor or Copilot rules to merge into this file right now. + +## Environment + +- Python: README says `3.10+`. +- Docker image: `python:3.13-slim`. +- Default web port: `8080`. +- App downloads `xray-core` on startup if `bin/xray` is missing. +- Runtime state is persisted under `config/` and `data/`. + +## Setup Commands + +### Local environment + +```bash +python3 -m venv venv +source venv/bin/activate +pip install -r requirements.txt +``` + +### Run locally + +```bash +python3 main.py +``` + +### Run via helper script + +```bash +chmod +x start.sh +./start.sh +``` + +### Build Docker image + +```bash +docker build -t airtosocks . +``` + +### Run with Docker Compose + +```bash +docker-compose up -d +docker-compose logs -f +docker-compose down +``` + +## Build, Lint, And Test Reality + +This repository currently has no formal lint or test setup. + +- No `tests/` directory was found. +- No `pytest` or `unittest` test files were found. +- No `ruff`, `black`, `isort`, `flake8`, `pylint`, `mypy`, `tox`, or `nox` config was found. + +Agents should say this explicitly instead of pretending a missing command exists. + +## Verification Commands That Work Today + +### Syntax check + +```bash +python3 -m py_compile main.py +``` + +### Compile Python files + +```bash +python3 -m compileall . +``` + +### Manual app run + +```bash +python3 main.py +``` + +### HTTP smoke checks + +```bash +curl http://127.0.0.1:8080/api/links +curl http://127.0.0.1:8080/api/subscriptions +curl http://127.0.0.1:8080/api/random +``` + +### Container smoke checks + +```bash +docker-compose up -d +curl http://127.0.0.1:8080/api/links +docker-compose logs --tail=200 +docker-compose down +``` + +## Single-Test Guidance + +There is no real single-test command yet because there is no test suite. + +If tests are added later, prefer `pytest` and document commands like: + +```bash +pytest +pytest tests/test_parser.py +pytest tests/test_parser.py::test_parse_vmess +``` + +Until then, a “single test” means a targeted manual verification of the exact parser, route, or runtime behavior touched by the change. + +## Config Notes + +Optional runtime config lives in `config.yaml` and currently supports: + +```yaml +port: 8080 +check_interval: 600 +``` + +When changing config behavior: + +- Preserve compatibility for missing keys. +- Keep defaults aligned across code, `README.md`, and `config.yaml.example`. +- Do not silently rename config fields. + +## Code Style + +The repo does not use an autoformatter today. Match the existing style unless the user asks for cleanup. + +### Imports + +- Keep standard library imports before third-party imports. +- Do not reorder the whole import block for cosmetic reasons. +- Add new imports near related imports. +- Prefer explicit imports over wildcard imports. + +### Formatting + +- Follow normal Python/PEP 8 conventions without broad reformatting. +- Use 4-space indentation. +- Preserve surrounding quote style; Python code here mostly uses single quotes. +- Avoid reflowing large inline HTML, CSS, or JS unless required. +- Keep diffs small and local. + +### Types + +- Continue the existing `typing` style: `Optional`, `List`, `Dict`, `Tuple`, `Any`. +- Add type hints for new helpers when practical. +- Use `@dataclass` for record-like state models. +- Do not introduce a new validation or typing framework without a concrete need. + +### Naming + +- `snake_case` for variables, functions, and methods. +- `PascalCase` for classes. +- `UPPER_SNAKE_CASE` for module constants. +- Use descriptive domain names like `subscription_id`, `socks_port`, `last_check`, `available_count`. + +### Data Modeling And Persistence + +- Persist structured state through dataclasses and `asdict(...)`. +- New persisted fields need safe defaults so old JSON continues to load. +- Consider compatibility with existing files in `config/`. +- Do not casually rename persisted fields. + +### Error Handling + +- Follow the existing pattern: catch failures near parsing, file I/O, network calls, and subprocess operations. +- Use `logger.error(...)` or `logger.warning(...)`, then return a safe fallback or JSON error. +- Avoid crashing daemon threads for recoverable problems. +- Prefer specific exceptions when obvious, but broad `except Exception as e` is already common here. +- API handlers should return meaningful JSON error payloads with sensible HTTP status codes. + +### Logging + +- Use the module-level `logger`, not `print`. +- Keep log messages short and operational. +- Include useful identifiers such as node name, subscription name, or port. +- Do not log secrets or full credentials from proxy links unless absolutely necessary. + +### Concurrency And Shared State + +- `ProxyManager` protects mutable state with `threading.RLock`; preserve that discipline. +- Be careful when changing `nodes`, `subscriptions`, `pools`, `used_ports`, or `xray_processes`. +- Keep background threads daemonized when matching the current pattern. +- Do not introduce async or heavier concurrency models for small tasks. + +### Files, Processes, And Runtime Data + +- Runtime files belong under `config/` or `data/`. +- Keep xray config generation deterministic and JSON-serializable. +- Follow the existing `subprocess.Popen(...)` pattern for xray processes. +- Release ports and clean up subprocess state on failure paths. + +### Flask And API Conventions + +- Keep Flask route handlers thin. +- Put behavior in `ProxyManager` or small helpers rather than in the route body. +- Return `jsonify(...)` for API responses. +- Validate request JSON defensively. +- Keep API response shapes stable unless a breaking change is explicitly requested. + +### Frontend Conventions + +- The frontend is intentionally inline inside the `/` route. +- For small UI changes, edit the embedded HTML/JS directly instead of adding a frontend build system. +- Preserve the current fetch-based interaction pattern. +- Keep the existing visual language unless the task asks for redesign work. + +### Comments And Docs + +- Existing comments/docstrings are short and often Chinese; match the surrounding language in touched code. +- Add comments only when behavior is not obvious from the code. +- Update `README.md`, `config.yaml.example`, and this file when commands or behavior change. + +## Change Strategy For Agents + +- Prefer surgical edits. +- Avoid splitting `main.py` into modules unless explicitly requested. +- Respect persisted data compatibility. +- Ignore unrelated runtime files unless the task specifically involves them. +- If you add tests or lint tooling, immediately document the new commands here. + +## Recommended Post-Change Checks + +For most Python-only changes: + +```bash +python3 -m py_compile main.py +``` + +For API behavior changes: + +1. Start the app with `python3 main.py`. +2. Hit the affected endpoint with `curl`. +3. Confirm there is no traceback and the response shape is correct. + +For Docker-related changes: + +```bash +docker build -t airtosocks . +docker-compose up -d +curl http://127.0.0.1:8080/api/links +docker-compose down +``` diff --git a/main.py b/main.py index 0015649..d6a3c4f 100644 --- a/main.py +++ b/main.py @@ -1067,6 +1067,7 @@ def index():
已加载节点池,请继续搜索并调整节点选择
'; + renderSelectedNodes(); + updatePoolSelectedCount(); + window.scrollTo({top: document.getElementById('poolName').offsetTop - 80, behavior: 'smooth'}); + } catch (e) { + showResult('加载节点池失败: ' + e.message); + } } async function createPool() { @@ -1423,23 +1459,20 @@ def index(): if (!name) return showResult('请输入池名称', 'info'); if (poolSelectedIds.size === 0) return showResult('请先搜索并选择节点', 'info'); try { - const resp = await fetch('/api/pools', { - method: 'POST', + const isEditing = !!editingPoolId; + const url = isEditing ? `/api/pools/${encodeURIComponent(editingPoolId)}` : '/api/pools'; + const resp = await fetch(url, { + method: isEditing ? 'PUT' : 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({name, node_ids: [...poolSelectedIds]}) }); const data = await resp.json(); if (!resp.ok) throw new Error(data.error); - showResult(`节点池"${name}"创建成功, ${data.node_ids.length} 个节点`, 'success'); - document.getElementById('poolName').value = ''; - document.getElementById('poolSearch').value = ''; - document.getElementById('poolSearchResults').innerHTML = ''; - document.getElementById('poolSelectedNodes').innerHTML = ''; - poolSelectedIds.clear(); - updatePoolSelectedCount(); + showResult(`节点池"${name}"${isEditing ? '修改' : '创建'}成功, ${data.node_ids.length} 个节点`, 'success'); + resetPoolEditor(); loadPools(); } catch (e) { - showResult('创建失败: ' + e.message); + showResult((editingPoolId ? '修改失败: ' : '创建失败: ') + e.message); } } @@ -1488,6 +1521,7 @@ def index():