拆分代码;约束cli命令
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
dist/
|
||||||
|
build/
|
||||||
|
__pycache__/
|
||||||
+31
-20
@@ -1,57 +1,68 @@
|
|||||||
# TJWater CLI
|
# TJWater CLI
|
||||||
|
|
||||||
独立于服务端主代码的 Python CLI 文件夹,放在 `TJWaterServerBinary/cli/` 下,供 agent 服务器**直接调用并通过 stdout/stderr 参与管道**。
|
独立于服务端主代码的 Python CLI 文件夹,放在 `TJWaterServerBinary/cli/` 下,供 agent 服务器使用**编译后的可执行文件**直接调用,并通过 stdout/stderr 参与管道。
|
||||||
|
|
||||||
## 直接使用
|
## 构建可执行产物
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd TJWaterServerBinary/cli
|
cd TJWaterServerBinary/cli
|
||||||
./tjwater help --json
|
python -m pip install -r requirements.txt
|
||||||
|
python -m pip install -r requirements-build.txt
|
||||||
|
chmod +x build.sh
|
||||||
|
./build.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
这个入口文件可以直接参与管道:
|
构建完成后,直接使用编译产物:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./tjwater help --json | jq
|
./dist/tjwater-cli/tjwater-cli help
|
||||||
```
|
```
|
||||||
|
|
||||||
它会优先使用:
|
这个可执行文件可以直接参与管道:
|
||||||
1. `cli/.venv/bin/python`
|
|
||||||
2. 环境变量 `PYTHON`
|
|
||||||
3. 当前环境里的 `python`
|
|
||||||
4. 最后回退到 `python3`
|
|
||||||
|
|
||||||
如果需要,也可以显式走 Python:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m tjwater_agent_cli help --json
|
./dist/tjwater-cli/tjwater-cli help | jq
|
||||||
|
```
|
||||||
|
|
||||||
|
当前采用 `PyInstaller onedir` 方式输出到 `dist/tjwater-cli/`,避免 onefile 在部分 agent/server 环境下依赖临时目录解包执行的问题。
|
||||||
|
|
||||||
|
如果需要在开发时直接走源码入口,也可以显式使用 Python:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m tjwater_cli help
|
||||||
```
|
```
|
||||||
|
|
||||||
## 部署到 agent 服务器
|
## 部署到 agent 服务器
|
||||||
|
|
||||||
最简单的方式是把整个 `TJWaterServerBinary/cli/` 文件夹同步到 agent 服务器,然后直接执行:
|
最简单的方式是把 `dist/tjwater-cli/` 整个目录同步到 agent 服务器,然后直接执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./tjwater-cli/tjwater-cli help
|
||||||
|
```
|
||||||
|
|
||||||
|
如果希望打包传输:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd TJWaterServerBinary/cli
|
cd TJWaterServerBinary/cli
|
||||||
./tjwater help --json
|
tar -C dist -czf tjwater-cli-linux-amd64.tar.gz tjwater-cli
|
||||||
```
|
```
|
||||||
|
|
||||||
如果希望放到 PATH 中:
|
如果希望放到 PATH 中:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
chmod +x tjwater
|
ln -s /path/to/TJWaterServerBinary/cli/dist/tjwater-cli/tjwater-cli /usr/local/bin/tjwater-cli
|
||||||
ln -s /path/to/TJWaterServerBinary/cli/tjwater /usr/local/bin/tjwater
|
tjwater-cli help | jq
|
||||||
tjwater help --json
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Python 依赖
|
## 运行与构建依赖
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd TJWaterServerBinary/cli
|
cd TJWaterServerBinary/cli
|
||||||
python -m pip install -r requirements.txt
|
python -m pip install -r requirements.txt
|
||||||
|
python -m pip install -r requirements-build.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
只保留运行 CLI 必需依赖,不再包含安装包构建相关内容。
|
`requirements.txt` 仅包含运行 CLI 的依赖;`requirements-build.txt` 仅包含生成可执行文件所需的构建依赖。
|
||||||
|
|
||||||
## 认证上下文
|
## 认证上下文
|
||||||
|
|
||||||
|
|||||||
Executable
+26
@@ -0,0 +1,26 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
if [ -n "${PYTHON:-}" ]; then
|
||||||
|
PYTHON_BIN="$PYTHON"
|
||||||
|
elif command -v python >/dev/null 2>&1; then
|
||||||
|
PYTHON_BIN="python"
|
||||||
|
else
|
||||||
|
PYTHON_BIN="python3"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$ROOT"
|
||||||
|
|
||||||
|
"$PYTHON_BIN" -m PyInstaller --noconfirm --clean tjwater.spec
|
||||||
|
|
||||||
|
BIN_PATH="$ROOT/dist/"
|
||||||
|
if [ ! -x "$BIN_PATH" ]; then
|
||||||
|
echo "build succeeded but executable was not created: $BIN_PATH" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
"$BIN_PATH" help >/dev/null
|
||||||
|
|
||||||
|
echo "built executable: $BIN_PATH"
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
from tjwater_cli.main import console_entry
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
console_entry()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pyinstaller>=6.11,<7
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from typer.testing import CliRunner
|
from typer.testing import CliRunner
|
||||||
@@ -64,61 +65,60 @@ def test_build_runtime_context_uses_default_server(monkeypatch):
|
|||||||
assert runtime.server == core.DEFAULT_SERVER
|
assert runtime.server == core.DEFAULT_SERVER
|
||||||
|
|
||||||
|
|
||||||
def test_help_json_lists_commands():
|
def test_help_outputs_json_lists_commands():
|
||||||
|
result = runner.invoke(app, ["help"])
|
||||||
|
payload = json.loads(result.stdout)
|
||||||
|
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert payload["schema_version"] == "tjwater-cli/v1"
|
||||||
|
assert any(command["command"] == "project" for command in payload["commands"])
|
||||||
|
assert any(command["command"] == "analysis" for command in payload["commands"])
|
||||||
|
assert payload["menu_level"] == 1
|
||||||
|
assert all(command["command"] != "project list" for command in payload["commands"])
|
||||||
|
|
||||||
|
|
||||||
|
def test_help_option_json_is_removed():
|
||||||
result = runner.invoke(app, ["help", "--json"])
|
result = runner.invoke(app, ["help", "--json"])
|
||||||
|
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 2
|
||||||
assert '"schema_version": "tjwater-cli/v1"' in result.stdout
|
assert "No such option: --json" in result.output
|
||||||
assert '"command": "project"' in result.stdout
|
|
||||||
assert '"command": "analysis"' in result.stdout
|
|
||||||
assert '"menu_level": 1' in result.stdout
|
|
||||||
assert '"command": "project list"' not in result.stdout
|
|
||||||
|
|
||||||
|
|
||||||
def test_help_defaults_to_text():
|
|
||||||
result = runner.invoke(app, ["help"])
|
|
||||||
|
|
||||||
assert result.exit_code == 0
|
|
||||||
assert "Commands:" in result.stdout
|
|
||||||
assert "project: 项目与项目级元数据相关命令。" in result.stdout
|
|
||||||
assert "analysis: 分析计算与诊断相关命令。" in result.stdout
|
|
||||||
assert "Use `tjwater <menu> help` to see subcommands." in result.stdout
|
|
||||||
assert "usage: tjwater project help" not in result.stdout
|
|
||||||
assert "example: tjwater project help" not in result.stdout
|
|
||||||
assert "project list: 列出当前用户可访问项目" not in result.stdout
|
|
||||||
assert '"schema_version": "tjwater-cli/v1"' not in result.stdout
|
|
||||||
|
|
||||||
|
|
||||||
def test_simulation_help_lists_subcommands():
|
def test_simulation_help_lists_subcommands():
|
||||||
result = runner.invoke(app, ["simulation", "help"])
|
result = runner.invoke(app, ["simulation", "help"])
|
||||||
|
payload = json.loads(result.stdout)
|
||||||
|
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert "模拟运行与调度相关命令。" in result.stdout
|
assert payload["summary"] == "模拟运行与调度相关命令。"
|
||||||
assert "simulation run: 触发指定绝对时间的模拟运行" in result.stdout
|
commands = {command["command"]: command for command in payload["commands"]}
|
||||||
assert "usage: tjwater simulation run --start-time <START_TIME> --duration <DURATION>" in result.stdout
|
assert commands["simulation run"]["summary"] == "触发指定绝对时间的模拟运行"
|
||||||
assert "example: tjwater --auth-context auth.json simulation run --start-time 2025-01-02T03:04:05+08:00 --duration 30" in result.stdout
|
assert commands["simulation run"]["usage"] == "tjwater-cli simulation run --start-time <START_TIME> --duration <DURATION>"
|
||||||
|
assert commands["simulation run"]["example"] == "tjwater-cli --auth-context auth.json simulation run --start-time 2025-01-02T03:04:05+08:00 --duration 30"
|
||||||
|
|
||||||
|
|
||||||
def test_nested_group_help_lists_examples():
|
def test_nested_group_help_lists_examples():
|
||||||
result = runner.invoke(app, ["analysis", "leakage", "help"])
|
result = runner.invoke(app, ["analysis", "leakage", "help"])
|
||||||
|
payload = json.loads(result.stdout)
|
||||||
|
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert "漏损分析相关命令。" in result.stdout
|
assert payload["summary"] == "漏损分析相关命令。"
|
||||||
assert "analysis leakage identify: 执行漏损识别" in result.stdout
|
commands = {command["command"]: command for command in payload["commands"]}
|
||||||
assert "example: tjwater --auth-context auth.json analysis leakage identify" in result.stdout
|
assert commands["analysis leakage identify"]["summary"] == "执行漏损识别"
|
||||||
|
assert commands["analysis leakage identify"]["example"] == "tjwater-cli --auth-context auth.json analysis leakage identify --start-time 2025-01-02T03:04:05+08:00 --end-time 2025-01-02T04:04:05+08:00"
|
||||||
|
|
||||||
|
|
||||||
def test_analysis_help_uses_group_summaries_for_nested_groups():
|
def test_analysis_help_uses_group_summaries_for_nested_groups():
|
||||||
result = runner.invoke(app, ["analysis", "help"])
|
result = runner.invoke(app, ["analysis", "help"])
|
||||||
|
payload = json.loads(result.stdout)
|
||||||
|
commands = {command["command"]: command for command in payload["commands"]}
|
||||||
|
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert "analysis leakage: 漏损分析相关命令。" in result.stdout
|
assert commands["analysis leakage"]["summary"] == "漏损分析相关命令。"
|
||||||
assert "analysis burst-detection: 爆管检测相关命令。" in result.stdout
|
assert commands["analysis burst-detection"]["summary"] == "爆管检测相关命令。"
|
||||||
assert "analysis burst-location" not in result.stdout
|
assert "analysis burst-location" not in commands
|
||||||
assert "analysis risk" not in result.stdout
|
assert "analysis risk" not in commands
|
||||||
assert "analysis leakage: 执行漏损识别" not in result.stdout
|
assert commands["analysis burst"]["example"] == "tjwater-cli --auth-context auth.json analysis burst --start-time 2025-01-02T03:04:05+08:00 --duration 30 --burst-file ./burst.json --scheme burst_case_01"
|
||||||
assert "example: tjwater --auth-context auth.json analysis burst --start-time 2025-01-02T03:04:05+08:00 --duration 30 --burst-file ./burst.json --scheme burst_case_01" in result.stdout
|
assert commands["analysis valve"]["example"] == "tjwater-cli --auth-context auth.json analysis valve --mode close --start-time 2025-01-02T03:04:05+08:00 --valve V1 --duration 900"
|
||||||
assert "example: tjwater --auth-context auth.json analysis valve --mode close --start-time 2025-01-02T03:04:05+08:00 --valve V1 --duration 900" in result.stdout
|
|
||||||
|
|
||||||
|
|
||||||
def test_bare_analysis_uses_typer_help_with_descriptions():
|
def test_bare_analysis_uses_typer_help_with_descriptions():
|
||||||
@@ -133,23 +133,48 @@ def test_bare_analysis_uses_typer_help_with_descriptions():
|
|||||||
assert "risk" not in result.stdout
|
assert "risk" not in result.stdout
|
||||||
|
|
||||||
|
|
||||||
def test_leaf_help_shows_usage_and_example():
|
def test_leaf_help_outputs_json():
|
||||||
result = runner.invoke(app, ["help", "simulation", "run"])
|
result = runner.invoke(app, ["help", "simulation", "run"])
|
||||||
|
payload = json.loads(result.stdout)
|
||||||
|
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert "Command: simulation run" in result.stdout
|
assert payload["command"] == "simulation run"
|
||||||
assert "结果需后续通过 data timeseries 在对应时间段查询" in result.stdout
|
assert payload["output"] == "模拟触发结果;实时数据需通过 data timeseries 命令按时间段查询"
|
||||||
assert "Usage: tjwater simulation run --start-time <START_TIME> --duration <DURATION>" in result.stdout
|
assert payload["usage"] == "tjwater-cli simulation run --start-time <START_TIME> --duration <DURATION>"
|
||||||
assert "Examples:" in result.stdout
|
assert payload["examples"] == ["tjwater-cli --auth-context auth.json simulation run --start-time 2025-01-02T03:04:05+08:00 --duration 30"]
|
||||||
assert "tjwater --auth-context auth.json simulation run --start-time 2025-01-02T03:04:05+08:00 --duration 30" in result.stdout
|
|
||||||
|
|
||||||
|
|
||||||
def test_project_help_uses_legal_kind_example():
|
def test_project_help_uses_legal_kind_example():
|
||||||
result = runner.invoke(app, ["project", "help"])
|
result = runner.invoke(app, ["project", "help"])
|
||||||
|
payload = json.loads(result.stdout)
|
||||||
|
commands = {command["command"]: command for command in payload["commands"]}
|
||||||
|
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert "example: tjwater --auth-context auth.json project data --kind scada-info" in result.stdout
|
assert commands["project data"]["example"] == "tjwater-cli --auth-context auth.json project data --kind scada-info"
|
||||||
assert "--kind time" not in result.stdout
|
assert "--kind time" not in commands["project data"]["example"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_root_help_flag_uses_typer_style_with_examples():
|
||||||
|
result = runner.invoke(app, ["--help"], prog_name="tjwater-cli")
|
||||||
|
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "Usage: tjwater-cli" in result.stdout
|
||||||
|
assert "Examples:" in result.stdout
|
||||||
|
assert "tjwater-cli help simulation run" in result.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def test_leaf_help_flag_includes_usage_and_example():
|
||||||
|
result = runner.invoke(app, ["simulation", "run", "--help"], prog_name="tjwater-cli")
|
||||||
|
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "Usage: tjwater-cli simulation run [OPTIONS]" in result.stdout
|
||||||
|
assert "Usage example:" in result.stdout
|
||||||
|
assert "--start-time <START_TIME>" in result.stdout
|
||||||
|
assert "--duration" in result.stdout
|
||||||
|
assert "Examples:" in result.stdout
|
||||||
|
assert "tjwater-cli simulation run" in result.stdout
|
||||||
|
assert "START_TIME" in result.stdout
|
||||||
|
assert "DURATION" in result.stdout
|
||||||
|
|
||||||
|
|
||||||
def test_analysis_burst_returns_next_step_to_fetch_scheme(monkeypatch, tmp_path: Path):
|
def test_analysis_burst_returns_next_step_to_fetch_scheme(monkeypatch, tmp_path: Path):
|
||||||
@@ -186,8 +211,8 @@ def test_analysis_burst_returns_next_step_to_fetch_scheme(monkeypatch, tmp_path:
|
|||||||
|
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert '"summary": "爆管分析执行成功"' in result.stdout
|
assert '"summary": "爆管分析执行成功"' in result.stdout
|
||||||
assert '"tjwater --auth-context auth.json data scheme get --name burst_case_01"' in result.stdout
|
assert '"tjwater-cli --auth-context auth.json data scheme get --name burst_case_01"' in result.stdout
|
||||||
assert '"tjwater --auth-context auth.json data scheme list"' in result.stdout
|
assert '"tjwater-cli --auth-context auth.json data scheme list"' in result.stdout
|
||||||
|
|
||||||
|
|
||||||
def test_main_missing_option_error_includes_usage_and_next_step(capsys):
|
def test_main_missing_option_error_includes_usage_and_next_step(capsys):
|
||||||
@@ -197,8 +222,8 @@ def test_main_missing_option_error_includes_usage_and_next_step(capsys):
|
|||||||
assert exit_code == 2
|
assert exit_code == 2
|
||||||
assert '"summary": "缺少参数"' in stdout
|
assert '"summary": "缺少参数"' in stdout
|
||||||
assert '"code": "MISSING_PARAMETER"' in stdout
|
assert '"code": "MISSING_PARAMETER"' in stdout
|
||||||
assert '"usage": "tjwater simulation run --start-time <START_TIME> --duration <DURATION>"' in stdout
|
assert '"usage": "tjwater-cli simulation run --start-time <START_TIME> --duration <DURATION>"' in stdout
|
||||||
assert '"tjwater help simulation run"' in stdout
|
assert '"tjwater-cli help simulation run"' in stdout
|
||||||
|
|
||||||
|
|
||||||
def test_main_bare_analysis_returns_typer_help_without_json_error(capsys):
|
def test_main_bare_analysis_returns_typer_help_without_json_error(capsys):
|
||||||
@@ -206,7 +231,7 @@ def test_main_bare_analysis_returns_typer_help_without_json_error(capsys):
|
|||||||
stdout = capsys.readouterr().out
|
stdout = capsys.readouterr().out
|
||||||
|
|
||||||
assert exit_code == 0
|
assert exit_code == 0
|
||||||
assert "Usage: tjwater analysis" in stdout
|
assert "Usage: tjwater-cli analysis" in stdout
|
||||||
assert "分析计算与诊断相关命令。" in stdout
|
assert "分析计算与诊断相关命令。" in stdout
|
||||||
assert '"ok": false' not in stdout
|
assert '"ok": false' not in stdout
|
||||||
|
|
||||||
@@ -268,8 +293,8 @@ def test_simulation_run_translates_rfc3339(monkeypatch, tmp_path: Path):
|
|||||||
"start_time": "03:04:05+08:00",
|
"start_time": "03:04:05+08:00",
|
||||||
"duration": 30,
|
"duration": 30,
|
||||||
}
|
}
|
||||||
assert '"tjwater --auth-context auth.json data timeseries realtime links --start-time 2025-01-02T03:04:05+08:00 --end-time 2025-01-02T03:34:05+08:00"' in result.stdout
|
assert '"tjwater-cli --auth-context auth.json data timeseries realtime links --start-time 2025-01-02T03:04:05+08:00 --end-time 2025-01-02T03:34:05+08:00"' in result.stdout
|
||||||
assert '"tjwater --auth-context auth.json data timeseries realtime nodes --start-time 2025-01-02T03:04:05+08:00 --end-time 2025-01-02T03:34:05+08:00"' in result.stdout
|
assert '"tjwater-cli --auth-context auth.json data timeseries realtime nodes --start-time 2025-01-02T03:04:05+08:00 --end-time 2025-01-02T03:34:05+08:00"' in result.stdout
|
||||||
|
|
||||||
|
|
||||||
def test_project_export_inp_downloads_file(monkeypatch, tmp_path: Path):
|
def test_project_export_inp_downloads_file(monkeypatch, tmp_path: Path):
|
||||||
|
|||||||
-17
@@ -1,17 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
|
|
||||||
if [ -x "$ROOT/.venv/bin/python" ]; then
|
|
||||||
PYTHON_BIN="$ROOT/.venv/bin/python"
|
|
||||||
elif [ -n "${PYTHON:-}" ]; then
|
|
||||||
PYTHON_BIN="$PYTHON"
|
|
||||||
elif command -v python >/dev/null 2>&1; then
|
|
||||||
PYTHON_BIN="python"
|
|
||||||
else
|
|
||||||
PYTHON_BIN="python3"
|
|
||||||
fi
|
|
||||||
|
|
||||||
export PYTHONPATH="$ROOT${PYTHONPATH:+:$PYTHONPATH}"
|
|
||||||
exec "$PYTHON_BIN" -m tjwater_agent_cli "$@"
|
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# -*- mode: python ; coding: utf-8 -*-
|
||||||
|
|
||||||
|
from PyInstaller.utils.hooks import collect_data_files
|
||||||
|
|
||||||
|
|
||||||
|
datas = collect_data_files("certifi")
|
||||||
|
|
||||||
|
|
||||||
|
a = Analysis(
|
||||||
|
["entrypoint.py"],
|
||||||
|
pathex=["."],
|
||||||
|
binaries=[],
|
||||||
|
datas=datas,
|
||||||
|
hiddenimports=[],
|
||||||
|
hookspath=[],
|
||||||
|
hooksconfig={},
|
||||||
|
runtime_hooks=[],
|
||||||
|
excludes=[],
|
||||||
|
noarchive=False,
|
||||||
|
optimize=0,
|
||||||
|
)
|
||||||
|
pyz = PYZ(a.pure)
|
||||||
|
|
||||||
|
exe = EXE(
|
||||||
|
pyz,
|
||||||
|
a.scripts,
|
||||||
|
[],
|
||||||
|
exclude_binaries=True,
|
||||||
|
name="tjwater-cli",
|
||||||
|
debug=False,
|
||||||
|
bootloader_ignore_signals=False,
|
||||||
|
strip=False,
|
||||||
|
upx=True,
|
||||||
|
console=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
coll = COLLECT(
|
||||||
|
exe,
|
||||||
|
a.binaries,
|
||||||
|
a.datas,
|
||||||
|
strip=False,
|
||||||
|
upx=True,
|
||||||
|
upx_exclude=[],
|
||||||
|
name="tjwater-cli",
|
||||||
|
)
|
||||||
+27
-25
@@ -2,31 +2,33 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import typer
|
import typer
|
||||||
|
|
||||||
app = typer.Typer(help="TJWater agent CLI", add_completion=False, no_args_is_help=True)
|
from .formatters import TJWaterGroup
|
||||||
project_app = typer.Typer(no_args_is_help=True)
|
|
||||||
network_app = typer.Typer(no_args_is_help=True)
|
app = typer.Typer(help="TJWater agent CLI", add_completion=False, no_args_is_help=True, cls=TJWaterGroup)
|
||||||
component_app = typer.Typer(no_args_is_help=True)
|
project_app = typer.Typer(no_args_is_help=True, cls=TJWaterGroup)
|
||||||
component_option_app = typer.Typer(no_args_is_help=True)
|
network_app = typer.Typer(no_args_is_help=True, cls=TJWaterGroup)
|
||||||
simulation_app = typer.Typer(no_args_is_help=True)
|
component_app = typer.Typer(no_args_is_help=True, cls=TJWaterGroup)
|
||||||
analysis_app = typer.Typer(no_args_is_help=True)
|
component_option_app = typer.Typer(no_args_is_help=True, cls=TJWaterGroup)
|
||||||
analysis_leakage_app = typer.Typer(no_args_is_help=True)
|
simulation_app = typer.Typer(no_args_is_help=True, cls=TJWaterGroup)
|
||||||
analysis_leakage_schemes_app = typer.Typer(no_args_is_help=True)
|
analysis_app = typer.Typer(no_args_is_help=True, cls=TJWaterGroup)
|
||||||
analysis_burst_detection_app = typer.Typer(no_args_is_help=True)
|
analysis_leakage_app = typer.Typer(no_args_is_help=True, cls=TJWaterGroup)
|
||||||
analysis_burst_detection_schemes_app = typer.Typer(no_args_is_help=True)
|
analysis_leakage_schemes_app = typer.Typer(no_args_is_help=True, cls=TJWaterGroup)
|
||||||
analysis_burst_location_app = typer.Typer(no_args_is_help=True)
|
analysis_burst_detection_app = typer.Typer(no_args_is_help=True, cls=TJWaterGroup)
|
||||||
analysis_burst_location_schemes_app = typer.Typer(no_args_is_help=True)
|
analysis_burst_detection_schemes_app = typer.Typer(no_args_is_help=True, cls=TJWaterGroup)
|
||||||
analysis_risk_app = typer.Typer(no_args_is_help=True)
|
analysis_burst_location_app = typer.Typer(no_args_is_help=True, cls=TJWaterGroup)
|
||||||
analysis_sensor_placement_app = typer.Typer(no_args_is_help=True)
|
analysis_burst_location_schemes_app = typer.Typer(no_args_is_help=True, cls=TJWaterGroup)
|
||||||
data_app = typer.Typer(no_args_is_help=True)
|
analysis_risk_app = typer.Typer(no_args_is_help=True, cls=TJWaterGroup)
|
||||||
data_timeseries_app = typer.Typer(no_args_is_help=True)
|
analysis_sensor_placement_app = typer.Typer(no_args_is_help=True, cls=TJWaterGroup)
|
||||||
data_timeseries_realtime_app = typer.Typer(no_args_is_help=True)
|
data_app = typer.Typer(no_args_is_help=True, cls=TJWaterGroup)
|
||||||
data_timeseries_scheme_app = typer.Typer(no_args_is_help=True)
|
data_timeseries_app = typer.Typer(no_args_is_help=True, cls=TJWaterGroup)
|
||||||
data_timeseries_scada_app = typer.Typer(no_args_is_help=True)
|
data_timeseries_realtime_app = typer.Typer(no_args_is_help=True, cls=TJWaterGroup)
|
||||||
data_timeseries_composite_app = typer.Typer(no_args_is_help=True)
|
data_timeseries_scheme_app = typer.Typer(no_args_is_help=True, cls=TJWaterGroup)
|
||||||
data_scada_app = typer.Typer(no_args_is_help=True)
|
data_timeseries_scada_app = typer.Typer(no_args_is_help=True, cls=TJWaterGroup)
|
||||||
data_scheme_app = typer.Typer(no_args_is_help=True)
|
data_timeseries_composite_app = typer.Typer(no_args_is_help=True, cls=TJWaterGroup)
|
||||||
data_extension_app = typer.Typer(no_args_is_help=True)
|
data_scada_app = typer.Typer(no_args_is_help=True, cls=TJWaterGroup)
|
||||||
data_misc_app = typer.Typer(no_args_is_help=True)
|
data_scheme_app = typer.Typer(no_args_is_help=True, cls=TJWaterGroup)
|
||||||
|
data_extension_app = typer.Typer(no_args_is_help=True, cls=TJWaterGroup)
|
||||||
|
data_misc_app = typer.Typer(no_args_is_help=True, cls=TJWaterGroup)
|
||||||
|
|
||||||
app.add_typer(project_app, name="project")
|
app.add_typer(project_app, name="project")
|
||||||
app.add_typer(network_app, name="network")
|
app.add_typer(network_app, name="network")
|
||||||
|
|||||||
@@ -58,8 +58,8 @@ def simulation_run(
|
|||||||
require_auth=True,
|
require_auth=True,
|
||||||
require_network_ctx=True,
|
require_network_ctx=True,
|
||||||
next_commands=[
|
next_commands=[
|
||||||
f"tjwater --auth-context auth.json data timeseries realtime links --start-time {parsed.isoformat()} --end-time {end_time}",
|
f"tjwater-cli --auth-context auth.json data timeseries realtime links --start-time {parsed.isoformat()} --end-time {end_time}",
|
||||||
f"tjwater --auth-context auth.json data timeseries realtime nodes --start-time {parsed.isoformat()} --end-time {end_time}",
|
f"tjwater-cli --auth-context auth.json data timeseries realtime nodes --start-time {parsed.isoformat()} --end-time {end_time}",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -92,8 +92,8 @@ def analysis_burst(
|
|||||||
require_auth=True,
|
require_auth=True,
|
||||||
require_network_ctx=True,
|
require_network_ctx=True,
|
||||||
next_commands=[
|
next_commands=[
|
||||||
f"tjwater --auth-context auth.json data scheme get --name {scheme_name}",
|
f"tjwater-cli --auth-context auth.json data scheme get --name {scheme_name}",
|
||||||
"tjwater --auth-context auth.json data scheme list",
|
"tjwater-cli --auth-context auth.json data scheme list",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ def project_export_inp(
|
|||||||
data={"output": str(output), "bytes": len(content)},
|
data={"output": str(output), "bytes": len(content)},
|
||||||
ctx=runtime,
|
ctx=runtime,
|
||||||
duration_ms=duration_dump + duration_download,
|
duration_ms=duration_dump + duration_download,
|
||||||
next_commands=["tjwater project info"],
|
next_commands=["tjwater-cli project info"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import requests
|
|||||||
import typer
|
import typer
|
||||||
|
|
||||||
SCHEMA_VERSION = "tjwater-cli/v1"
|
SCHEMA_VERSION = "tjwater-cli/v1"
|
||||||
|
CLI_NAME = "tjwater-cli"
|
||||||
DEFAULT_TIMEOUT = 60
|
DEFAULT_TIMEOUT = 60
|
||||||
DEFAULT_SERVER = "http://192.168.1.114:8000"
|
DEFAULT_SERVER = "http://192.168.1.114:8000"
|
||||||
|
|
||||||
@@ -180,7 +181,7 @@ def require_access_token(ctx: RuntimeContext) -> str:
|
|||||||
code="UNAUTHENTICATED",
|
code="UNAUTHENTICATED",
|
||||||
message="missing access token for agent context",
|
message="missing access token for agent context",
|
||||||
exit_code=3,
|
exit_code=3,
|
||||||
next_commands=["tjwater <command> --auth-context /path/to/auth-context.json"],
|
next_commands=["tjwater-cli <command> --auth-context /path/to/auth-context.json"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import click
|
||||||
|
import typer.core
|
||||||
|
|
||||||
|
|
||||||
|
class TJWaterGroup(typer.core.TyperGroup):
|
||||||
|
def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
|
||||||
|
super().format_help(ctx, formatter)
|
||||||
|
from .helping import build_group_help_appendix
|
||||||
|
|
||||||
|
appendix = build_group_help_appendix(ctx)
|
||||||
|
if appendix:
|
||||||
|
formatter.write_paragraph()
|
||||||
|
formatter.write_text(appendix)
|
||||||
+78
-64
@@ -37,7 +37,7 @@ def context_command_path(click_ctx: click.Context | None) -> tuple[str, ...]:
|
|||||||
|
|
||||||
def _build_click_context(path: tuple[str, ...]) -> click.Context | None:
|
def _build_click_context(path: tuple[str, ...]) -> click.Context | None:
|
||||||
root = _click_root_command()
|
root = _click_root_command()
|
||||||
ctx: click.Context = click.Context(root, info_name="tjwater")
|
ctx: click.Context = click.Context(root, info_name="tjwater-cli")
|
||||||
command: click.Command = root
|
command: click.Command = root
|
||||||
for token in path:
|
for token in path:
|
||||||
if not isinstance(command, click.Group):
|
if not isinstance(command, click.Group):
|
||||||
@@ -54,7 +54,7 @@ def build_usage(path: tuple[str, ...]) -> str | None:
|
|||||||
ctx = _build_click_context(path)
|
ctx = _build_click_context(path)
|
||||||
if ctx is None:
|
if ctx is None:
|
||||||
return None
|
return None
|
||||||
parts = ["tjwater", *path]
|
parts = ["tjwater-cli", *path]
|
||||||
for parameter in ctx.command.params:
|
for parameter in ctx.command.params:
|
||||||
if not isinstance(parameter, click.Option):
|
if not isinstance(parameter, click.Option):
|
||||||
continue
|
continue
|
||||||
@@ -165,7 +165,7 @@ def _build_example(path: tuple[str, ...], *, existing_examples: list[str] | None
|
|||||||
has_required_options = all(f"--{option_name}" in example for option_name in required_option_names)
|
has_required_options = all(f"--{option_name}" in example for option_name in required_option_names)
|
||||||
if has_auth and has_required_options:
|
if has_auth and has_required_options:
|
||||||
return example
|
return example
|
||||||
parts = ["tjwater", "--auth-context", "auth.json", *path]
|
parts = ["tjwater-cli", "--auth-context", "auth.json", *path]
|
||||||
if ctx is None:
|
if ctx is None:
|
||||||
return " ".join(parts)
|
return " ".join(parts)
|
||||||
for parameter in ctx.command.params:
|
for parameter in ctx.command.params:
|
||||||
@@ -198,8 +198,8 @@ def _enrich_index_payload(payload: dict[str, Any]) -> dict[str, Any]:
|
|||||||
path = tuple(command_item["command"].split())
|
path = tuple(command_item["command"].split())
|
||||||
doc = get_command_doc(path)
|
doc = get_command_doc(path)
|
||||||
if doc is None and has_subcommands(path):
|
if doc is None and has_subcommands(path):
|
||||||
command_item["usage"] = f"tjwater {' '.join(path)} help"
|
command_item["usage"] = f"tjwater-cli {' '.join(path)} help"
|
||||||
command_item["example"] = f"tjwater {' '.join(path)} help"
|
command_item["example"] = f"tjwater-cli {' '.join(path)} help"
|
||||||
else:
|
else:
|
||||||
existing_examples = [] if doc is None else list(doc.get("examples", []))
|
existing_examples = [] if doc is None else list(doc.get("examples", []))
|
||||||
command_item["usage"] = build_usage(path) or command_item.get("usage")
|
command_item["usage"] = build_usage(path) or command_item.get("usage")
|
||||||
@@ -220,11 +220,8 @@ def resolve_help_payload(path: tuple[str, ...]) -> tuple[dict[str, Any] | None,
|
|||||||
return None, False
|
return None, False
|
||||||
|
|
||||||
|
|
||||||
def emit_help_payload(payload: dict[str, Any], *, json_output: bool, is_index: bool) -> None:
|
def emit_help_payload(payload: dict[str, Any]) -> None:
|
||||||
if json_output:
|
typer.echo(json.dumps(payload, ensure_ascii=False))
|
||||||
typer.echo(json.dumps(payload, ensure_ascii=False))
|
|
||||||
else:
|
|
||||||
typer.echo(render_help_text(payload, is_index=is_index))
|
|
||||||
|
|
||||||
|
|
||||||
def merge_next_commands(*groups: list[str] | None) -> list[str]:
|
def merge_next_commands(*groups: list[str] | None) -> list[str]:
|
||||||
@@ -259,12 +256,12 @@ def build_error_guidance(click_ctx: click.Context | None) -> tuple[Any, list[str
|
|||||||
return (
|
return (
|
||||||
{
|
{
|
||||||
"command_group": " ".join(group_path),
|
"command_group": " ".join(group_path),
|
||||||
"usage": f"tjwater {' '.join(group_path)} help",
|
"usage": f"tjwater-cli {' '.join(group_path)} help",
|
||||||
"examples": [f"tjwater {' '.join(group_path)} help", f"tjwater help {' '.join(group_path)}"],
|
"examples": [f"tjwater-cli {' '.join(group_path)} help", f"tjwater-cli help {' '.join(group_path)}"],
|
||||||
},
|
},
|
||||||
merge_next_commands(
|
merge_next_commands(
|
||||||
[f"tjwater {' '.join(group_path)} help", f"tjwater help {' '.join(group_path)}"],
|
[f"tjwater-cli {' '.join(group_path)} help", f"tjwater-cli help {' '.join(group_path)}"],
|
||||||
["tjwater help"],
|
["tjwater-cli help"],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
payload, is_index = resolve_help_payload(command_path)
|
payload, is_index = resolve_help_payload(command_path)
|
||||||
@@ -275,21 +272,21 @@ def build_error_guidance(click_ctx: click.Context | None) -> tuple[Any, list[str
|
|||||||
"usage": payload.get("usage") or usage,
|
"usage": payload.get("usage") or usage,
|
||||||
"examples": payload.get("examples", []),
|
"examples": payload.get("examples", []),
|
||||||
},
|
},
|
||||||
merge_next_commands([f"tjwater help {' '.join(command_path)}"], ["tjwater help"]),
|
merge_next_commands([f"tjwater-cli help {' '.join(command_path)}"], ["tjwater-cli help"]),
|
||||||
)
|
)
|
||||||
if payload is not None and is_index:
|
if payload is not None and is_index:
|
||||||
return (
|
return (
|
||||||
{
|
{
|
||||||
"command_group": " ".join(command_path),
|
"command_group": " ".join(command_path),
|
||||||
"usage": f"tjwater {' '.join(command_path)} help",
|
"usage": f"tjwater-cli {' '.join(command_path)} help",
|
||||||
"examples": [f"tjwater {' '.join(command_path)} help", f"tjwater help {' '.join(command_path)}"],
|
"examples": [f"tjwater-cli {' '.join(command_path)} help", f"tjwater-cli help {' '.join(command_path)}"],
|
||||||
},
|
},
|
||||||
merge_next_commands(
|
merge_next_commands(
|
||||||
[f"tjwater {' '.join(command_path)} help", f"tjwater help {' '.join(command_path)}"],
|
[f"tjwater-cli {' '.join(command_path)} help", f"tjwater-cli help {' '.join(command_path)}"],
|
||||||
["tjwater help"],
|
["tjwater-cli help"],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return ({"usage": usage} if usage else None, ["tjwater help"])
|
return ({"usage": usage} if usage else None, ["tjwater-cli help"])
|
||||||
|
|
||||||
|
|
||||||
def classify_click_error(exc: click.ClickException) -> tuple[str, str]:
|
def classify_click_error(exc: click.ClickException) -> tuple[str, str]:
|
||||||
@@ -305,55 +302,61 @@ def classify_click_error(exc: click.ClickException) -> tuple[str, str]:
|
|||||||
return "CLI 参数错误", "USAGE_ERROR"
|
return "CLI 参数错误", "USAGE_ERROR"
|
||||||
|
|
||||||
|
|
||||||
def render_help_text(payload: dict[str, Any], *, is_index: bool) -> str:
|
def _build_root_help_epilog() -> str:
|
||||||
lines: list[str] = [str(payload.get("summary", ""))]
|
return "\n".join(
|
||||||
if is_index:
|
[
|
||||||
is_top_level = payload.get("menu_level") == 1
|
"\b",
|
||||||
lines.append("")
|
"Examples:",
|
||||||
lines.append("Commands:")
|
" tjwater-cli help",
|
||||||
for command in payload.get("commands", []):
|
" tjwater-cli help simulation run",
|
||||||
lines.append(f" {command['command']}: {command['summary']}")
|
" tjwater-cli simulation run --help",
|
||||||
if not is_top_level and command.get("usage"):
|
]
|
||||||
lines.append(f" usage: {command['usage']}")
|
)
|
||||||
if not is_top_level and command.get("example"):
|
|
||||||
lines.append(f" example: {command['example']}")
|
|
||||||
lines.append("")
|
|
||||||
if is_top_level:
|
|
||||||
lines.append("Use `tjwater <menu> help` to see subcommands.")
|
|
||||||
else:
|
|
||||||
lines.append("Use `tjwater help --json` for structured output.")
|
|
||||||
return "\n".join(lines)
|
|
||||||
|
|
||||||
lines.append("")
|
|
||||||
lines.append(f"Command: {payload['command']}")
|
|
||||||
lines.append(f"Description: {payload['description']}")
|
|
||||||
if payload.get("usage"):
|
|
||||||
lines.append(f"Usage: {payload['usage']}")
|
|
||||||
|
|
||||||
options = payload.get("options", [])
|
|
||||||
if options:
|
|
||||||
lines.append("")
|
|
||||||
lines.append("Options:")
|
|
||||||
for option in options:
|
|
||||||
suffix = " (required)" if option.get("required") else ""
|
|
||||||
lines.append(f" --{option['name']}{suffix}: {option['description']}")
|
|
||||||
|
|
||||||
|
def _build_leaf_help_epilog(path: tuple[str, ...], payload: dict[str, Any]) -> str:
|
||||||
|
lines = ["\b"]
|
||||||
|
description = payload.get("description")
|
||||||
|
usage = payload.get("usage")
|
||||||
examples = payload.get("examples", [])
|
examples = payload.get("examples", [])
|
||||||
|
next_commands = payload.get("next_commands", [])
|
||||||
|
if description:
|
||||||
|
lines.extend([f"Description: {description}", ""])
|
||||||
|
if usage:
|
||||||
|
lines.extend([f"Usage example: {usage}", ""])
|
||||||
if examples:
|
if examples:
|
||||||
lines.append("")
|
|
||||||
lines.append("Examples:")
|
lines.append("Examples:")
|
||||||
for example in examples:
|
lines.extend(f" {example}" for example in examples)
|
||||||
lines.append(f" {example}")
|
lines.append("")
|
||||||
|
if next_commands:
|
||||||
lines.append("")
|
lines.append("Next steps:")
|
||||||
lines.append("Use `tjwater help --json` for structured output.")
|
lines.extend(f" {command}" for command in next_commands)
|
||||||
|
lines.append("")
|
||||||
|
lines.extend(["Structured JSON:", f" tjwater-cli help {' '.join(path)}"])
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_group_help_epilog(path: tuple[str, ...], payload: dict[str, Any]) -> str:
|
||||||
|
lines = ["\b", "Examples:", f" tjwater-cli help {' '.join(path)}"]
|
||||||
|
for command in payload.get("commands", [])[:2]:
|
||||||
|
example = command.get("example")
|
||||||
|
if example:
|
||||||
|
lines.append(f" {example}")
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def build_group_help_appendix(click_ctx: click.Context | None) -> str | None:
|
||||||
|
path = context_command_path(click_ctx)
|
||||||
|
if not path:
|
||||||
|
return _build_root_help_epilog()
|
||||||
|
payload, is_index = resolve_help_payload(path)
|
||||||
|
if payload is None or not is_index:
|
||||||
|
return None
|
||||||
|
return _build_group_help_epilog(path, payload)
|
||||||
|
|
||||||
|
|
||||||
def make_group_help_handler(path_prefix: tuple[str, ...]):
|
def make_group_help_handler(path_prefix: tuple[str, ...]):
|
||||||
def group_help(
|
def group_help() -> None:
|
||||||
json_output: Annotated[bool, typer.Option("--json", help="输出 JSON")] = False,
|
|
||||||
) -> None:
|
|
||||||
payload, is_index = resolve_help_payload(path_prefix)
|
payload, is_index = resolve_help_payload(path_prefix)
|
||||||
if payload is None:
|
if payload is None:
|
||||||
raise CLIError(
|
raise CLIError(
|
||||||
@@ -361,9 +364,9 @@ def make_group_help_handler(path_prefix: tuple[str, ...]):
|
|||||||
code="COMMAND_NOT_FOUND",
|
code="COMMAND_NOT_FOUND",
|
||||||
message=f"unknown command path: {' '.join(path_prefix)}",
|
message=f"unknown command path: {' '.join(path_prefix)}",
|
||||||
exit_code=2,
|
exit_code=2,
|
||||||
next_commands=["tjwater help"],
|
next_commands=["tjwater-cli help"],
|
||||||
)
|
)
|
||||||
emit_help_payload(payload, json_output=json_output, is_index=is_index)
|
emit_help_payload(payload)
|
||||||
|
|
||||||
group_help.__name__ = f"{'_'.join(path_prefix)}_help"
|
group_help.__name__ = f"{'_'.join(path_prefix)}_help"
|
||||||
return group_help
|
return group_help
|
||||||
@@ -375,19 +378,30 @@ def register_group_help_commands() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def apply_typer_help_metadata() -> None:
|
def apply_typer_help_metadata() -> None:
|
||||||
app.help = "TJWater agent CLI"
|
app.help = "\n".join(
|
||||||
|
[
|
||||||
|
"TJWater agent CLI",
|
||||||
|
"",
|
||||||
|
"Examples:",
|
||||||
|
" tjwater-cli help",
|
||||||
|
" tjwater-cli help simulation run",
|
||||||
|
" tjwater-cli simulation run --help",
|
||||||
|
]
|
||||||
|
)
|
||||||
app.short_help = "TJWater agent CLI"
|
app.short_help = "TJWater agent CLI"
|
||||||
for group_app, path_prefix in GROUP_HELP_APPS:
|
for group_app, path_prefix in GROUP_HELP_APPS:
|
||||||
for command_info in group_app.registered_commands:
|
for command_info in group_app.registered_commands:
|
||||||
command_path = (*path_prefix, command_info.name)
|
command_path = (*path_prefix, command_info.name)
|
||||||
if command_info.name == "help":
|
if command_info.name == "help":
|
||||||
command_info.help = f"显示 {' '.join(path_prefix)} 的帮助信息。"
|
command_info.help = f"输出 {' '.join(path_prefix)} 的 JSON 帮助信息。"
|
||||||
command_info.short_help = command_info.help
|
command_info.short_help = command_info.help
|
||||||
|
command_info.epilog = "\n".join(["\b", "Example:", f" tjwater-cli help {' '.join(path_prefix)}"])
|
||||||
command_info.hidden = False
|
command_info.hidden = False
|
||||||
continue
|
continue
|
||||||
payload = get_command_doc(command_path)
|
payload = get_command_doc(command_path)
|
||||||
command_info.help = None if payload is None else str(payload.get("summary", ""))
|
command_info.help = None if payload is None else str(payload.get("summary", ""))
|
||||||
command_info.short_help = command_info.help
|
command_info.short_help = command_info.help
|
||||||
|
command_info.epilog = None if payload is None else _build_leaf_help_epilog(command_path, payload)
|
||||||
command_info.hidden = is_hidden_path(command_path)
|
command_info.hidden = is_hidden_path(command_path)
|
||||||
for group_info in group_app.registered_groups:
|
for group_info in group_app.registered_groups:
|
||||||
group_path = (*path_prefix, group_info.name)
|
group_path = (*path_prefix, group_info.name)
|
||||||
|
|||||||
+7
-10
@@ -44,11 +44,8 @@ def root_callback(
|
|||||||
register_group_help_commands()
|
register_group_help_commands()
|
||||||
|
|
||||||
|
|
||||||
@app.command("help", context_settings={"allow_extra_args": True, "ignore_unknown_options": True})
|
@app.command("help", context_settings={"allow_extra_args": True})
|
||||||
def help_command(
|
def help_command(ctx: typer.Context) -> None:
|
||||||
ctx: typer.Context,
|
|
||||||
json_output: Annotated[bool, typer.Option("--json", help="输出 JSON")] = False,
|
|
||||||
) -> None:
|
|
||||||
command_path = list(ctx.args)
|
command_path = list(ctx.args)
|
||||||
payload, is_index = resolve_help_payload(tuple(command_path))
|
payload, is_index = resolve_help_payload(tuple(command_path))
|
||||||
if payload is None:
|
if payload is None:
|
||||||
@@ -61,13 +58,13 @@ def help_command(
|
|||||||
server=None,
|
server=None,
|
||||||
request_id=None,
|
request_id=None,
|
||||||
data={
|
data={
|
||||||
"usage": "tjwater help <command-path>",
|
"usage": "tjwater-cli help <command-path>",
|
||||||
"examples": ["tjwater help simulation run", "tjwater simulation help"],
|
"examples": ["tjwater-cli help simulation run", "tjwater-cli simulation help"],
|
||||||
},
|
},
|
||||||
next_commands=["tjwater help", "tjwater help simulation"],
|
next_commands=["tjwater-cli help", "tjwater-cli help simulation"],
|
||||||
)
|
)
|
||||||
raise typer.Exit(code=2)
|
raise typer.Exit(code=2)
|
||||||
emit_help_payload(payload, json_output=json_output, is_index=is_index)
|
emit_help_payload(payload)
|
||||||
|
|
||||||
|
|
||||||
# Must run at import time because tests call runner.invoke(app, ...) directly.
|
# Must run at import time because tests call runner.invoke(app, ...) directly.
|
||||||
@@ -76,7 +73,7 @@ apply_typer_help_metadata()
|
|||||||
|
|
||||||
def main(argv: list[str] | None = None) -> int:
|
def main(argv: list[str] | None = None) -> int:
|
||||||
try:
|
try:
|
||||||
app(args=argv if argv is not None else sys.argv[1:], prog_name="tjwater", standalone_mode=False)
|
app(args=argv if argv is not None else sys.argv[1:], prog_name="tjwater-cli", standalone_mode=False)
|
||||||
return 0
|
return 0
|
||||||
except CLIError as exc:
|
except CLIError as exc:
|
||||||
click_ctx = click.get_current_context(silent=True)
|
click_ctx = click.get_current_context(silent=True)
|
||||||
|
|||||||
+12
-12
@@ -39,15 +39,15 @@ COMMAND_DOCS: dict[tuple[str, ...], CommandDoc] = {
|
|||||||
path=("project", "list"),
|
path=("project", "list"),
|
||||||
summary="列出当前用户可访问项目",
|
summary="列出当前用户可访问项目",
|
||||||
description="调用 /meta/projects 返回项目列表。",
|
description="调用 /meta/projects 返回项目列表。",
|
||||||
examples=("tjwater --auth-context auth.json project list",),
|
examples=("tjwater-cli --auth-context auth.json project list",),
|
||||||
next_commands=("tjwater --auth-context auth.json project info",),
|
next_commands=("tjwater-cli --auth-context auth.json project info",),
|
||||||
output="项目摘要列表",
|
output="项目摘要列表",
|
||||||
),
|
),
|
||||||
("project", "info"): CommandDoc(
|
("project", "info"): CommandDoc(
|
||||||
path=("project", "info"),
|
path=("project", "info"),
|
||||||
summary="读取当前项目元数据",
|
summary="读取当前项目元数据",
|
||||||
description="调用 /meta/project 返回当前 project 详情。",
|
description="调用 /meta/project 返回当前 project 详情。",
|
||||||
examples=("tjwater --auth-context auth.json project info",),
|
examples=("tjwater-cli --auth-context auth.json project info",),
|
||||||
output="项目元数据",
|
output="项目元数据",
|
||||||
),
|
),
|
||||||
("project", "db-health"): CommandDoc(
|
("project", "db-health"): CommandDoc(
|
||||||
@@ -109,8 +109,8 @@ COMMAND_DOCS: dict[tuple[str, ...], CommandDoc] = {
|
|||||||
CommandOptionDoc("duration", "持续分钟数", required=True),
|
CommandOptionDoc("duration", "持续分钟数", required=True),
|
||||||
),
|
),
|
||||||
next_commands=(
|
next_commands=(
|
||||||
"tjwater --auth-context auth.json data timeseries realtime links --start-time 2025-01-02T03:04:05+08:00 --end-time 2025-01-02T03:34:05+08:00",
|
"tjwater-cli --auth-context auth.json data timeseries realtime links --start-time 2025-01-02T03:04:05+08:00 --end-time 2025-01-02T03:34:05+08:00",
|
||||||
"tjwater --auth-context auth.json data timeseries realtime nodes --start-time 2025-01-02T03:04:05+08:00 --end-time 2025-01-02T03:34:05+08:00",
|
"tjwater-cli --auth-context auth.json data timeseries realtime nodes --start-time 2025-01-02T03:04:05+08:00 --end-time 2025-01-02T03:34:05+08:00",
|
||||||
),
|
),
|
||||||
output="模拟触发结果;实时数据需通过 data timeseries 命令按时间段查询",
|
output="模拟触发结果;实时数据需通过 data timeseries 命令按时间段查询",
|
||||||
),
|
),
|
||||||
@@ -125,11 +125,11 @@ COMMAND_DOCS: dict[tuple[str, ...], CommandDoc] = {
|
|||||||
CommandOptionDoc("scheme", "方案名称"),
|
CommandOptionDoc("scheme", "方案名称"),
|
||||||
),
|
),
|
||||||
examples=(
|
examples=(
|
||||||
"tjwater --auth-context auth.json analysis burst --start-time 2025-01-02T03:04:05+08:00 --duration 30 --burst-file ./burst.json --scheme burst_case_01",
|
"tjwater-cli --auth-context auth.json analysis burst --start-time 2025-01-02T03:04:05+08:00 --duration 30 --burst-file ./burst.json --scheme burst_case_01",
|
||||||
),
|
),
|
||||||
next_commands=(
|
next_commands=(
|
||||||
"tjwater --auth-context auth.json data scheme get --name burst_case_01",
|
"tjwater-cli --auth-context auth.json data scheme get --name burst_case_01",
|
||||||
"tjwater --auth-context auth.json data scheme list",
|
"tjwater-cli --auth-context auth.json data scheme list",
|
||||||
),
|
),
|
||||||
output="分析执行结果;方案详情需通过 data scheme 命令单独查询",
|
output="分析执行结果;方案详情需通过 data scheme 命令单独查询",
|
||||||
),
|
),
|
||||||
@@ -138,7 +138,7 @@ COMMAND_DOCS: dict[tuple[str, ...], CommandDoc] = {
|
|||||||
summary="执行阀门关闭或隔离分析",
|
summary="执行阀门关闭或隔离分析",
|
||||||
description="mode=close 使用 valve 列表;mode=isolation 需要 accident element,可选 disabled-valve。",
|
description="mode=close 使用 valve 列表;mode=isolation 需要 accident element,可选 disabled-valve。",
|
||||||
examples=(
|
examples=(
|
||||||
"tjwater --auth-context auth.json analysis valve --mode close --start-time 2025-01-02T03:04:05+08:00 --valve V1 --duration 900",
|
"tjwater-cli --auth-context auth.json analysis valve --mode close --start-time 2025-01-02T03:04:05+08:00 --valve V1 --duration 900",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
("analysis", "flushing"): CommandDoc(
|
("analysis", "flushing"): CommandDoc(
|
||||||
@@ -422,13 +422,13 @@ def list_subcommands(path_prefix: tuple[str, ...], summary: str | None = None) -
|
|||||||
seen.add(subcommand)
|
seen.add(subcommand)
|
||||||
current_path = (*path_prefix, subcommand)
|
current_path = (*path_prefix, subcommand)
|
||||||
is_group = has_subcommands(current_path)
|
is_group = has_subcommands(current_path)
|
||||||
usage = f"tjwater {' '.join(current_path)} help" if is_group else (doc.examples[0] if doc.examples else _build_usage(doc))
|
usage = f"tjwater-cli {' '.join(current_path)} help" if is_group else (doc.examples[0] if doc.examples else _build_usage(doc))
|
||||||
commands.append(
|
commands.append(
|
||||||
{
|
{
|
||||||
"command": " ".join(current_path),
|
"command": " ".join(current_path),
|
||||||
"summary": get_group_summary(current_path) if is_group else doc.summary,
|
"summary": get_group_summary(current_path) if is_group else doc.summary,
|
||||||
"usage": usage,
|
"usage": usage,
|
||||||
"example": f"tjwater {' '.join(current_path)} help" if is_group else _build_examples(doc)[0],
|
"example": f"tjwater-cli {' '.join(current_path)} help" if is_group else _build_examples(doc)[0],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
@@ -440,7 +440,7 @@ def list_subcommands(path_prefix: tuple[str, ...], summary: str | None = None) -
|
|||||||
|
|
||||||
|
|
||||||
def _build_usage(doc: CommandDoc) -> str:
|
def _build_usage(doc: CommandDoc) -> str:
|
||||||
parts = ["tjwater", *doc.path]
|
parts = ["tjwater-cli", *doc.path]
|
||||||
for option in doc.options:
|
for option in doc.options:
|
||||||
placeholder = option.name.upper().replace("-", "_")
|
placeholder = option.name.upper().replace("-", "_")
|
||||||
if option.required:
|
if option.required:
|
||||||
|
|||||||
@@ -7,13 +7,13 @@
|
|||||||
首批 CLI 采用 **少量顶层入口 + 业务域二级分组 + 只读/分析优先** 的设计。
|
首批 CLI 采用 **少量顶层入口 + 业务域二级分组 + 只读/分析优先** 的设计。
|
||||||
|
|
||||||
```text
|
```text
|
||||||
tjwater project
|
tjwater-cli project
|
||||||
tjwater network
|
tjwater-cli network
|
||||||
tjwater component
|
tjwater-cli component
|
||||||
tjwater simulation
|
tjwater-cli simulation
|
||||||
tjwater analysis
|
tjwater-cli analysis
|
||||||
tjwater data
|
tjwater-cli data
|
||||||
tjwater help
|
tjwater-cli help
|
||||||
```
|
```
|
||||||
|
|
||||||
首批默认不暴露:
|
首批默认不暴露:
|
||||||
@@ -45,12 +45,12 @@ tjwater help
|
|||||||
| `simulation` | `run` | 模拟运行 |
|
| `simulation` | `run` | 模拟运行 |
|
||||||
| `analysis` | `burst`、`valve`、`flushing`、`age`、`contaminant`、`sensor-placement`、`leakage`、`burst-detection`、`burst-location`、`risk` | 任务级分析 |
|
| `analysis` | `burst`、`valve`、`flushing`、`age`、`contaminant`、`sensor-placement`、`leakage`、`burst-detection`、`burst-location`、`risk` | 任务级分析 |
|
||||||
| `data` | `timeseries`、`scada`、`scheme`、`extension`、`misc` | 数据查询 |
|
| `data` | `timeseries`、`scada`、`scheme`、`extension`、`misc` | 数据查询 |
|
||||||
| `help` | `--json`、`COMMAND --json` | Agent 能力发现和命令说明 |
|
| `help` | `COMMAND` | Agent 能力发现和命令说明 |
|
||||||
|
|
||||||
命令深度建议:
|
命令深度建议:
|
||||||
|
|
||||||
- 常规命令不超过 3 层:`tjwater component option get`
|
- 常规命令不超过 3 层:`tjwater-cli component option get`
|
||||||
- 时序数据允许 4 层:`tjwater data timeseries realtime links`
|
- 时序数据允许 4 层:`tjwater-cli data timeseries realtime links`
|
||||||
- `risk` 归入 `analysis risk`
|
- `risk` 归入 `analysis risk`
|
||||||
- `scada`、`scheme`、`extension` 归入 `data`
|
- `scada`、`scheme`、`extension` 归入 `data`
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ tjwater help
|
|||||||
- 用户输入的业务时间默认按 **UTC+8** 理解;若命令直接接收完整时间戳,应使用 ISO 8601 / RFC 3339 并显式包含时区。CLI 可直接传 `+08:00`,也可传其他时区的绝对时间,由服务端统一归一化。
|
- 用户输入的业务时间默认按 **UTC+8** 理解;若命令直接接收完整时间戳,应使用 ISO 8601 / RFC 3339 并显式包含时区。CLI 可直接传 `+08:00`,也可传其他时区的绝对时间,由服务端统一归一化。
|
||||||
- 范围参数优先拆成 `--start-time` / `--end-time`,不再引入模糊的 `--time-range ...` 写法。
|
- 范围参数优先拆成 `--start-time` / `--end-time`,不再引入模糊的 `--time-range ...` 写法。
|
||||||
- 复合输入优先使用可重复显式选项或 `--input FILE`,避免把多个语义字段压进 `ID:SIZE`、`NODE:VALUE`、`VALVE:OPENING` 这类 shell 内联 DSL。
|
- 复合输入优先使用可重复显式选项或 `--input FILE`,避免把多个语义字段压进 `ID:SIZE`、`NODE:VALUE`、`VALVE:OPENING` 这类 shell 内联 DSL。
|
||||||
- 若必须传大批量复合参数,优先支持 `--input FILE`,文件格式由 `help --json` 给出 schema。
|
- 若必须传大批量复合参数,优先支持 `--input FILE`,文件格式由 `help` 给出 schema。
|
||||||
|
|
||||||
## 首批 CLI 范围
|
## 首批 CLI 范围
|
||||||
|
|
||||||
@@ -138,11 +138,11 @@ Agent 调用认证上下文:
|
|||||||
|
|
||||||
| 命令 | 覆盖接口 | 说明 |
|
| 命令 | 覆盖接口 | 说明 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `tjwater project list` | `GET /meta/projects` | 项目列表 |
|
| `tjwater-cli project list` | `GET /meta/projects` | 项目列表 |
|
||||||
| `tjwater project info` | `GET /meta/project` | 当前 project 信息 |
|
| `tjwater-cli project info` | `GET /meta/project` | 当前 project 信息 |
|
||||||
| `tjwater project db-health` | `GET /meta/db/health` | 当前 project 数据库健康 |
|
| `tjwater-cli project db-health` | `GET /meta/db/health` | 当前 project 数据库健康 |
|
||||||
| `tjwater project export-inp --output PATH` | `GET /exportinp/`、`GET /dumpinp/`、`GET /downloadinp/` | 导出当前 project 的 INP 到本地文件 |
|
| `tjwater-cli project export-inp --output PATH` | `GET /exportinp/`、`GET /dumpinp/`、`GET /downloadinp/` | 导出当前 project 的 INP 到本地文件 |
|
||||||
| `tjwater project data --kind scada-info\|scheme-list\|burst-locate-result` | `GET /scada-info`、`GET /scheme-list`、`GET /burst-locate-result*` | 当前 project 的业务数据 |
|
| `tjwater-cli project data --kind scada-info\|scheme-list\|burst-locate-result` | `GET /scada-info`、`GET /scheme-list`、`GET /burst-locate-result*` | 当前 project 的业务数据 |
|
||||||
|
|
||||||
暂不暴露:
|
暂不暴露:
|
||||||
|
|
||||||
@@ -181,8 +181,8 @@ app/api/v1/endpoints/network/*.py
|
|||||||
|
|
||||||
| 命令 | 覆盖接口 | 说明 |
|
| 命令 | 覆盖接口 | 说明 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `tjwater network get-node-properties --node NODE` | `GET /getnodeproperties/` | 读取当前 project 中指定节点的属性 |
|
| `tjwater-cli network get-node-properties --node NODE` | `GET /getnodeproperties/` | 读取当前 project 中指定节点的属性 |
|
||||||
| `tjwater network get-link-properties --link LINK` | `GET /getlinkproperties/` | 读取当前 project 中指定管线的属性 |
|
| `tjwater-cli network get-link-properties --link LINK` | `GET /getlinkproperties/` | 读取当前 project 中指定管线的属性 |
|
||||||
|
|
||||||
暂不暴露:
|
暂不暴露:
|
||||||
|
|
||||||
@@ -209,14 +209,14 @@ app/api/v1/endpoints/components/*.py
|
|||||||
|
|
||||||
| 命令 | 覆盖接口 | 说明 |
|
| 命令 | 覆盖接口 | 说明 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `tjwater component option schema --kind time` | `GET /gettimeschema` | 时间选项 schema |
|
| `tjwater-cli component option schema --kind time` | `GET /gettimeschema` | 时间选项 schema |
|
||||||
| `tjwater component option get --kind time` | `GET /gettimeproperties/` | 时间选项属性 |
|
| `tjwater-cli component option get --kind time` | `GET /gettimeproperties/` | 时间选项属性 |
|
||||||
| `tjwater component option schema --kind energy` | `GET /getenergyschema/` | 全局能耗选项 schema |
|
| `tjwater-cli component option schema --kind energy` | `GET /getenergyschema/` | 全局能耗选项 schema |
|
||||||
| `tjwater component option get --kind energy` | `GET /getenergyproperties/` | 全局能耗选项属性 |
|
| `tjwater-cli component option get --kind energy` | `GET /getenergyproperties/` | 全局能耗选项属性 |
|
||||||
| `tjwater component option schema --kind pump-energy` | `GET /getpumpenergyschema/` | 泵能耗选项 schema |
|
| `tjwater-cli component option schema --kind pump-energy` | `GET /getpumpenergyschema/` | 泵能耗选项 schema |
|
||||||
| `tjwater component option get --kind pump-energy --pump PUMP` | `GET /getpumpenergyproperties//` | 指定泵的能耗选项属性 |
|
| `tjwater-cli component option get --kind pump-energy --pump PUMP` | `GET /getpumpenergyproperties//` | 指定泵的能耗选项属性 |
|
||||||
| `tjwater component option schema --kind network` | `GET /getoptionschema/` | 管网选项 schema |
|
| `tjwater-cli component option schema --kind network` | `GET /getoptionschema/` | 管网选项 schema |
|
||||||
| `tjwater component option get --kind network` | `GET /getoptionproperties/` | 管网选项属性 |
|
| `tjwater-cli component option get --kind network` | `GET /getoptionproperties/` | 管网选项属性 |
|
||||||
|
|
||||||
暂不暴露:
|
暂不暴露:
|
||||||
|
|
||||||
@@ -273,22 +273,22 @@ app/api/v1/endpoints/risk.py
|
|||||||
|
|
||||||
| 命令 | 覆盖接口 | 说明 |
|
| 命令 | 覆盖接口 | 说明 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `tjwater simulation run --start-time RFC3339 --duration MINUTES` | `POST /runsimulationmanuallybydate/` | 按指定绝对开始时间触发当前 project 的实时模拟;`start-time` 必须显式带时区,结果写入服务端时序库,后续通过 `tjwater data timeseries realtime *` 查询 |
|
| `tjwater-cli simulation run --start-time RFC3339 --duration MINUTES` | `POST /runsimulationmanuallybydate/` | 按指定绝对开始时间触发当前 project 的实时模拟;`start-time` 必须显式带时区,结果写入服务端时序库,后续通过 `tjwater-cli data timeseries realtime *` 查询 |
|
||||||
| `tjwater analysis burst --start-time TIME --duration SEC --scheme SCHEME --burst-file FILE` | `GET /burst_analysis/` | 爆管分析;`FILE` 提供爆管点与流量列表,CLI 负责转换为 `burst_ID[]` / `burst_size[]` |
|
| `tjwater-cli analysis burst --start-time TIME --duration SEC --scheme SCHEME --burst-file FILE` | `GET /burst_analysis/` | 爆管分析;`FILE` 提供爆管点与流量列表,CLI 负责转换为 `burst_ID[]` / `burst_size[]` |
|
||||||
| `tjwater analysis valve --mode close\|isolation --start-time TIME --valve VALVE` | `GET /valve_close_analysis/`、`GET /valve_isolation_analysis/` | 阀门分析,`--valve` 可重复 |
|
| `tjwater-cli analysis valve --mode close\|isolation --start-time TIME --valve VALVE` | `GET /valve_close_analysis/`、`GET /valve_isolation_analysis/` | 阀门分析,`--valve` 可重复 |
|
||||||
| `tjwater analysis flushing --start-time TIME --valve-setting-file FILE --drainage-node NODE --flow FLOW [--duration SEC] [--scheme SCHEME]` | `GET /flushing_analysis/` | 冲洗分析;`FILE` 提供阀门与开度列表,CLI 负责转换为 `valves[]` / `valves_k[]` |
|
| `tjwater-cli analysis flushing --start-time TIME --valve-setting-file FILE --drainage-node NODE --flow FLOW [--duration SEC] [--scheme SCHEME]` | `GET /flushing_analysis/` | 冲洗分析;`FILE` 提供阀门与开度列表,CLI 负责转换为 `valves[]` / `valves_k[]` |
|
||||||
| `tjwater analysis age --start-time TIME --duration SEC` | `GET /age_analysis/` | 水龄分析 |
|
| `tjwater-cli analysis age --start-time TIME --duration SEC` | `GET /age_analysis/` | 水龄分析 |
|
||||||
| `tjwater analysis contaminant --start-time TIME --duration SEC --source-node NODE --concentration VALUE [--pattern PATTERN] [--scheme SCHEME]` | `GET /contaminant_simulation/` | 污染物模拟 |
|
| `tjwater-cli analysis contaminant --start-time TIME --duration SEC --source-node NODE --concentration VALUE [--pattern PATTERN] [--scheme SCHEME]` | `GET /contaminant_simulation/` | 污染物模拟 |
|
||||||
| `tjwater analysis sensor-placement kmeans --count N` | `GET /pressuresensorplacementkmeans/` | 基于 kmeans 的传感器放置分析;不包含创建方案 |
|
| `tjwater-cli analysis sensor-placement kmeans --count N` | `GET /pressuresensorplacementkmeans/` | 基于 kmeans 的传感器放置分析;不包含创建方案 |
|
||||||
| `tjwater analysis leakage identify --scheme SCHEME --start-time TIME --end-time TIME` | `POST /leakage/identify/` | 漏损识别 |
|
| `tjwater-cli analysis leakage identify --scheme SCHEME --start-time TIME --end-time TIME` | `POST /leakage/identify/` | 漏损识别 |
|
||||||
| `tjwater analysis leakage schemes list\|get` | `GET /leakage/schemes/`、`GET /leakage/schemes/{scheme_name}` | 漏损方案查询 |
|
| `tjwater-cli analysis leakage schemes list\|get` | `GET /leakage/schemes/`、`GET /leakage/schemes/{scheme_name}` | 漏损方案查询 |
|
||||||
| `tjwater analysis burst-detection detect --scheme SCHEME --start-time TIME --end-time TIME` | `POST /burst-detection/detect/` | 爆管检测 |
|
| `tjwater-cli analysis burst-detection detect --scheme SCHEME --start-time TIME --end-time TIME` | `POST /burst-detection/detect/` | 爆管检测 |
|
||||||
| `tjwater analysis burst-detection schemes list\|get` | `GET /burst-detection/schemes/`、`GET /burst-detection/schemes/{scheme_name}` | 爆管检测方案查询 |
|
| `tjwater-cli analysis burst-detection schemes list\|get` | `GET /burst-detection/schemes/`、`GET /burst-detection/schemes/{scheme_name}` | 爆管检测方案查询 |
|
||||||
| `tjwater analysis burst-location locate --scheme SCHEME --start-time TIME --end-time TIME` | `POST /burst-location/locate/` | 爆管定位 |
|
| `tjwater-cli analysis burst-location locate --scheme SCHEME --start-time TIME --end-time TIME` | `POST /burst-location/locate/` | 爆管定位 |
|
||||||
| `tjwater analysis burst-location schemes list\|get` | `GET /burst-location/schemes/`、`GET /burst-location/schemes/{scheme_name}` | 爆管定位方案查询 |
|
| `tjwater-cli analysis burst-location schemes list\|get` | `GET /burst-location/schemes/`、`GET /burst-location/schemes/{scheme_name}` | 爆管定位方案查询 |
|
||||||
| `tjwater analysis risk pipe-now --pipe PIPE` | `GET /getpiperiskprobabilitynow/` | 单条管道当前风险 |
|
| `tjwater-cli analysis risk pipe-now --pipe PIPE` | `GET /getpiperiskprobabilitynow/` | 单条管道当前风险 |
|
||||||
| `tjwater analysis risk pipe-history --pipe PIPE` | `GET /getpiperiskprobability/` | 单条管道历史风险 |
|
| `tjwater-cli analysis risk pipe-history --pipe PIPE` | `GET /getpiperiskprobability/` | 单条管道历史风险 |
|
||||||
| `tjwater analysis risk network` | `GET /getnetworkpiperiskprobabilitynow/`、`GET /getpiperiskprobabilitygeometries/` | 当前 project 全网风险 |
|
| `tjwater-cli analysis risk network` | `GET /getnetworkpiperiskprobabilitynow/`、`GET /getpiperiskprobabilitygeometries/` | 当前 project 全网风险 |
|
||||||
|
|
||||||
暂缓或暂不暴露:
|
暂缓或暂不暴露:
|
||||||
|
|
||||||
@@ -310,7 +310,7 @@ POST /daily_scheduling_analysis/
|
|||||||
- `simulation run` 不直接回传全量模拟结果;它负责触发服务端模拟,并返回执行摘要、时间窗口和后续查询提示。
|
- `simulation run` 不直接回传全量模拟结果;它负责触发服务端模拟,并返回执行摘要、时间窗口和后续查询提示。
|
||||||
- 当前 `runsimulationmanuallybydate` 接口会从 `start_time` 指定的绝对时间开始,按 15 分钟步长运行直到达到 `duration`,结果持久化到服务端时序存储。
|
- 当前 `runsimulationmanuallybydate` 接口会从 `start_time` 指定的绝对时间开始,按 15 分钟步长运行直到达到 `duration`,结果持久化到服务端时序存储。
|
||||||
- `start_time` 必须显式带时区;CLI 推荐直接传 **UTC+8** 时间,服务端统一转换后执行和落库。CLI 文档与帮助信息需要把这条规则写成显式契约,不能把数据库存储时间直接暴露成用户输入语义。
|
- `start_time` 必须显式带时区;CLI 推荐直接传 **UTC+8** 时间,服务端统一转换后执行和落库。CLI 文档与帮助信息需要把这条规则写成显式契约,不能把数据库存储时间直接暴露成用户输入语义。
|
||||||
- 模拟结果读取统一走 `tjwater data timeseries realtime *`,而不是再单独设计 `simulation output`。
|
- 模拟结果读取统一走 `tjwater-cli data timeseries realtime *`,而不是再单独设计 `simulation output`。
|
||||||
- `analysis` 相关命令首批也按同步请求处理;若后续服务端真的引入任务队列,再单独设计 `job` 类基础设施能力。
|
- `analysis` 相关命令首批也按同步请求处理;若后续服务端真的引入任务队列,再单独设计 `job` 类基础设施能力。
|
||||||
|
|
||||||
### Data
|
### Data
|
||||||
@@ -328,22 +328,22 @@ app/api/v1/endpoints/project_data.py
|
|||||||
|
|
||||||
| 命令 | 覆盖接口 | 说明 |
|
| 命令 | 覆盖接口 | 说明 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `tjwater data timeseries realtime links --start-time TIME --end-time TIME` | `GET /realtime/links` | 查询指定时间范围内的实时/模拟管道数据 |
|
| `tjwater-cli data timeseries realtime links --start-time TIME --end-time TIME` | `GET /realtime/links` | 查询指定时间范围内的实时/模拟管道数据 |
|
||||||
| `tjwater data timeseries realtime nodes --start-time TIME --end-time TIME` | `GET /realtime/nodes` | 查询指定时间范围内的实时/模拟节点数据 |
|
| `tjwater-cli data timeseries realtime nodes --start-time TIME --end-time TIME` | `GET /realtime/nodes` | 查询指定时间范围内的实时/模拟节点数据 |
|
||||||
| `tjwater data timeseries realtime simulation-by-id-time --id ID --type pipe\|junction --time TIME` | `GET /realtime/query/by-id-time` | 查询指定元素在指定时间点的模拟结果 |
|
| `tjwater-cli data timeseries realtime simulation-by-id-time --id ID --type pipe\|junction --time TIME` | `GET /realtime/query/by-id-time` | 查询指定元素在指定时间点的模拟结果 |
|
||||||
| `tjwater data timeseries realtime simulation-by-time-property --type pipe\|junction --time TIME --property PROPERTY` | `GET /realtime/query/by-time-property` | 查询指定时间点某类元素某属性的聚合模拟结果 |
|
| `tjwater-cli data timeseries realtime simulation-by-time-property --type pipe\|junction --time TIME --property PROPERTY` | `GET /realtime/query/by-time-property` | 查询指定时间点某类元素某属性的聚合模拟结果 |
|
||||||
| `tjwater data timeseries scheme links --scheme SCHEME --start-time TIME --end-time TIME` | `GET /scheme/links`、`GET /scheme/links/{link_id}/field` | 方案管道数据 |
|
| `tjwater-cli data timeseries scheme links --scheme SCHEME --start-time TIME --end-time TIME` | `GET /scheme/links`、`GET /scheme/links/{link_id}/field` | 方案管道数据 |
|
||||||
| `tjwater data timeseries scheme node-field --node NODE --field FIELD` | `GET /scheme/nodes/{node_id}/field` | 方案节点字段 |
|
| `tjwater-cli data timeseries scheme node-field --node NODE --field FIELD` | `GET /scheme/nodes/{node_id}/field` | 方案节点字段 |
|
||||||
| `tjwater data timeseries scheme simulation --query by-id-time\|by-scheme-time-property --scheme SCHEME --id ID --time TIME --property PROPERTY` | `GET /scheme/query/*` | 方案模拟查询 |
|
| `tjwater-cli data timeseries scheme simulation --query by-id-time\|by-scheme-time-property --scheme SCHEME --id ID --time TIME --property PROPERTY` | `GET /scheme/query/*` | 方案模拟查询 |
|
||||||
| `tjwater data timeseries scada query --device-id ID --start-time TIME --end-time TIME [--device-id ID ...] [--field FIELD]` | `GET /scada/by-ids-time-range`、`GET /scada/by-ids-field-time-range` | SCADA 时序;CLI 把重复 `--device-id` 转换为后端逗号分隔参数 |
|
| `tjwater-cli data timeseries scada query --device-id ID --start-time TIME --end-time TIME [--device-id ID ...] [--field FIELD]` | `GET /scada/by-ids-time-range`、`GET /scada/by-ids-field-time-range` | SCADA 时序;CLI 把重复 `--device-id` 转换为后端逗号分隔参数 |
|
||||||
| `tjwater data timeseries composite --kind scada-simulation\|element-simulation\|element-scada --feature FEATURE --start-time TIME --end-time TIME` | `GET /composite/*` | 复合查询,`--feature` 可重复 |
|
| `tjwater-cli data timeseries composite --kind scada-simulation\|element-simulation\|element-scada --feature FEATURE --start-time TIME --end-time TIME` | `GET /composite/*` | 复合查询,`--feature` 可重复 |
|
||||||
| `tjwater data timeseries composite pipeline-health --pipe PIPE --start-time TIME --end-time TIME` | `GET /composite/pipeline-health-prediction` | 管道健康预测 |
|
| `tjwater-cli data timeseries composite pipeline-health --pipe PIPE --start-time TIME --end-time TIME` | `GET /composite/pipeline-health-prediction` | 管道健康预测 |
|
||||||
| `tjwater data scada schema --kind device\|device-data\|element\|info` | `GET /getscada*schema/` | `SCADA` 元数据 `schema` |
|
| `tjwater-cli data scada schema --kind device\|device-data\|element\|info` | `GET /getscada*schema/` | `SCADA` 元数据 `schema` |
|
||||||
| `tjwater data scada get\|list --kind device\|device-data\|element\|info` | `scada.py` 下 `GET` 查询接口 | `SCADA` 元数据 |
|
| `tjwater-cli data scada get\|list --kind device\|device-data\|element\|info` | `scada.py` 下 `GET` 查询接口 | `SCADA` 元数据 |
|
||||||
| `tjwater data scheme schema\|get\|list` | `schemes.py` 下 `GET` 接口 | 当前 project 方案查询 |
|
| `tjwater-cli data scheme schema\|get\|list` | `schemes.py` 下 `GET` 接口 | 当前 project 方案查询 |
|
||||||
| `tjwater data extension keys\|get\|list` | `extension.py` 下 `GET` 查询接口 | 当前 project 扩展数据查询 |
|
| `tjwater-cli data extension keys\|get\|list` | `extension.py` 下 `GET` 查询接口 | 当前 project 扩展数据查询 |
|
||||||
| `tjwater data misc sensor-placements` | `GET /getallsensorplacements/` | 当前 project 传感器位置 |
|
| `tjwater-cli data misc sensor-placements` | `GET /getallsensorplacements/` | 当前 project 传感器位置 |
|
||||||
| `tjwater data misc burst-location-results` | `GET /getallburstlocateresults/` | 当前 project 爆管定位结果 |
|
| `tjwater-cli data misc burst-location-results` | `GET /getallburstlocateresults/` | 当前 project 爆管定位结果 |
|
||||||
|
|
||||||
- `realtime` 是首批 simulation 结果的主读取域;CLI 可以按任务语义组合 `links`、`nodes`、`simulation-by-id-time`、`simulation-by-time-property`,但底层数据源仍以 `realtime.py` 为准。
|
- `realtime` 是首批 simulation 结果的主读取域;CLI 可以按任务语义组合 `links`、`nodes`、`simulation-by-id-time`、`simulation-by-time-property`,但底层数据源仍以 `realtime.py` 为准。
|
||||||
- `realtime`、`scheme`、`composite` 等时间查询命令面向用户时仍按 **UTC+8** 输入;CLI/服务端负责转换为后端使用的 **UTC0** 条件进行检索。若返回结果直接包含时间戳,必须显式带时区,避免把存储时间和展示时间混淆。
|
- `realtime`、`scheme`、`composite` 等时间查询命令面向用户时仍按 **UTC+8** 输入;CLI/服务端负责转换为后端使用的 **UTC0** 条件进行检索。若返回结果直接包含时间戳,必须显式带时区,避免把存储时间和展示时间混淆。
|
||||||
@@ -434,8 +434,8 @@ POST /users/{user_id}/deactivate
|
|||||||
|
|
||||||
| 命令 | 说明 |
|
| 命令 | 说明 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `tjwater help --json` | 返回当前 CLI 能力清单,供 Agent 发现可用命令 |
|
| `tjwater-cli help` | 返回当前 CLI 能力清单,供 Agent 发现可用命令 |
|
||||||
| `tjwater help COMMAND --json` | 返回某个命令的参数、输出、示例和推荐后续命令 |
|
| `tjwater-cli help COMMAND` | 返回某个命令的参数、输出、示例和推荐后续命令 |
|
||||||
|
|
||||||
输出补充约束:
|
输出补充约束:
|
||||||
|
|
||||||
@@ -491,7 +491,7 @@ POST /users/{user_id}/deactivate
|
|||||||
"data": null,
|
"data": null,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"next_commands": [
|
"next_commands": [
|
||||||
"tjwater <command> --auth-context /path/to/auth-context.json"
|
"tjwater-cli <command> --auth-context /path/to/auth-context.json"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -500,7 +500,7 @@ POST /users/{user_id}/deactivate
|
|||||||
|
|
||||||
- `metadata` 至少建议包含:`request_id`、`server`、`duration_ms`、`generated_at`。
|
- `metadata` 至少建议包含:`request_id`、`server`、`duration_ms`、`generated_at`。
|
||||||
- `next_commands` 是面向 agent 的推荐后续动作,不影响退出码和主结果语义。
|
- `next_commands` 是面向 agent 的推荐后续动作,不影响退出码和主结果语义。
|
||||||
- 所有 `help --json` 输出也应带 `schema_version`,便于 agent 做能力协商。
|
- 所有 `help` 输出也应带 `schema_version`,便于 agent 做能力协商。
|
||||||
|
|
||||||
## 后续开放条件
|
## 后续开放条件
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user