aukeys-opscli 0.0.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aukeys_opscli-0.0.6.dist-info/METADATA +15 -0
- aukeys_opscli-0.0.6.dist-info/RECORD +92 -0
- aukeys_opscli-0.0.6.dist-info/WHEEL +4 -0
- aukeys_opscli-0.0.6.dist-info/entry_points.txt +2 -0
- opscli/__init__.py +9 -0
- opscli/amazon/__init__.py +13 -0
- opscli/amazon/cli.py +5 -0
- opscli/amazon/client.py +5 -0
- opscli/amazon/commands/__init__.py +1 -0
- opscli/amazon/commands/cli.py +200 -0
- opscli/amazon/domain/__init__.py +25 -0
- opscli/amazon/domain/exceptions.py +73 -0
- opscli/amazon/domain/models.py +74 -0
- opscli/amazon/exceptions.py +21 -0
- opscli/amazon/manager.py +5 -0
- opscli/amazon/models.py +5 -0
- opscli/amazon/parser.py +5 -0
- opscli/amazon/scraper.py +5 -0
- opscli/amazon/scraping/__init__.py +12 -0
- opscli/amazon/scraping/parser.py +66 -0
- opscli/amazon/scraping/scraper.py +315 -0
- opscli/amazon/services/__init__.py +5 -0
- opscli/amazon/services/manager.py +105 -0
- opscli/amazon/transport/__init__.py +5 -0
- opscli/amazon/transport/client.py +77 -0
- opscli/auth/__init__.py +79 -0
- opscli/auth/cli.py +255 -0
- opscli/auth/commands/__init__.py +1 -0
- opscli/auth/commands/cli.py +5 -0
- opscli/auth/config.py +55 -0
- opscli/auth/core/__init__.py +1 -0
- opscli/auth/core/device_flow.py +80 -0
- opscli/auth/core/system_registry.py +89 -0
- opscli/auth/core/token_manager.py +167 -0
- opscli/auth/domain/__init__.py +23 -0
- opscli/auth/domain/exceptions.py +33 -0
- opscli/auth/exceptions.py +23 -0
- opscli/auth/storage/__init__.py +1 -0
- opscli/auth/storage/credential_store.py +163 -0
- opscli/auth/storage/crypto.py +67 -0
- opscli/cli.py +36 -0
- opscli/config.py +3 -0
- opscli/query/__init__.py +6 -0
- opscli/query/cli.py +5 -0
- opscli/query/client.py +5 -0
- opscli/query/commands/__init__.py +1 -0
- opscli/query/commands/cli.py +140 -0
- opscli/query/domain/__init__.py +23 -0
- opscli/query/domain/exceptions.py +73 -0
- opscli/query/domain/models.py +21 -0
- opscli/query/exceptions.py +21 -0
- opscli/query/manager.py +5 -0
- opscli/query/models.py +5 -0
- opscli/query/services/__init__.py +5 -0
- opscli/query/services/manager.py +373 -0
- opscli/query/transport/__init__.py +5 -0
- opscli/query/transport/client.py +57 -0
- opscli/skills/__init__.py +37 -0
- opscli/skills/cli.py +5 -0
- opscli/skills/commands/__init__.py +1 -0
- opscli/skills/commands/cli.py +389 -0
- opscli/skills/detector.py +5 -0
- opscli/skills/discovery/__init__.py +5 -0
- opscli/skills/discovery/detector.py +225 -0
- opscli/skills/domain/__init__.py +23 -0
- opscli/skills/domain/exceptions.py +51 -0
- opscli/skills/domain/models.py +144 -0
- opscli/skills/exceptions.py +5 -0
- opscli/skills/manager.py +5 -0
- opscli/skills/models.py +19 -0
- opscli/skills/services/__init__.py +5 -0
- opscli/skills/services/manager.py +276 -0
- opscli/skills/sync/__init__.py +5 -0
- opscli/skills/sync/updater.py +274 -0
- opscli/skills/templates/ops-amazon/SKILL.md +181 -0
- opscli/skills/templates/ops-amazon/data/VERSION.json +4 -0
- opscli/skills/templates/ops-auth/SKILL.md +466 -0
- opscli/skills/templates/ops-auth/data/VERSION.json +4 -0
- opscli/skills/templates/ops-dataset-query/SKILL.md +691 -0
- opscli/skills/templates/ops-dataset-query/data/VERSION.json +4 -0
- opscli/skills/templates/ops-dataset-query/data/dataset_fields.csv +1 -0
- opscli/skills/templates/ops-dataset-query/data/datasets.csv +1 -0
- opscli/skills/templates/ops-dataset-query/data/query_metadata.json +4 -0
- opscli/skills/templates/ops-dataset-query/references//346/225/260/346/215/256/346/237/245/350/257/242/346/234/215/345/212/241/345/274/200/345/217/221/350/257/264/346/230/216/346/226/207/346/241/243.md +1126 -0
- opscli/skills/templates/ops-dataset-query/scripts/core.py +140 -0
- opscli/skills/templates/ops-dataset-query/scripts/query.py +145 -0
- opscli/skills/templates/ops-dataset-query/scripts/search.py +36 -0
- opscli/skills/templates/ops-dataset-query/scripts/updater.py +106 -0
- opscli/skills/templates/ops-skills/SKILL.md +494 -0
- opscli/skills/templates/ops-skills/data/VERSION.json +4 -0
- opscli/skills/updater.py +5 -0
- opscli/version.py +19 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""ops-dataset-query Skill 的核心工具函数。
|
|
2
|
+
|
|
3
|
+
提供 CSV 加载、过滤和搜索打分能力,供 search.py 和 updater.py 复用。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import csv
|
|
9
|
+
import re
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def load_csv_rows(path: Path) -> list[dict]:
|
|
14
|
+
"""加载 CSV 文件为字典列表。
|
|
15
|
+
|
|
16
|
+
使用 utf-8-sig 编码以兼容带 BOM 的 CSV 文件。
|
|
17
|
+
文件不存在时返回空列表。
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
path: CSV 文件路径
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
每行一个字典,键名为 CSV 表头
|
|
24
|
+
"""
|
|
25
|
+
if not path.exists():
|
|
26
|
+
return []
|
|
27
|
+
with path.open("r", encoding="utf-8-sig", newline="") as handle:
|
|
28
|
+
return list(csv.DictReader(handle))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def filter_rows_by_dataset(rows: list[dict], dataset: str | None) -> list[dict]:
|
|
32
|
+
"""按数据集别名过滤行。
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
rows: 原始字段行
|
|
36
|
+
dataset: 数据集别名;为空时不过滤
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
过滤后的字段行
|
|
40
|
+
"""
|
|
41
|
+
if not dataset:
|
|
42
|
+
return rows
|
|
43
|
+
|
|
44
|
+
normalized = dataset.strip().lower()
|
|
45
|
+
if not normalized:
|
|
46
|
+
return rows
|
|
47
|
+
|
|
48
|
+
return [
|
|
49
|
+
row for row in rows
|
|
50
|
+
if str(row.get("dataset_alias", "")).strip().lower() == normalized
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def search_rows(rows: list[dict], keyword: str, limit: int = 10) -> list[dict]:
|
|
55
|
+
"""按关键词搜索字段行,并返回按相关性排序后的结果。
|
|
56
|
+
|
|
57
|
+
排序策略使用简单加权打分,优先匹配:
|
|
58
|
+
- `field_name`
|
|
59
|
+
- `verbose_name`
|
|
60
|
+
- `keywords`
|
|
61
|
+
- `description`
|
|
62
|
+
- 其他整行内容
|
|
63
|
+
"""
|
|
64
|
+
normalized = keyword.strip().lower()
|
|
65
|
+
if not normalized:
|
|
66
|
+
return []
|
|
67
|
+
|
|
68
|
+
tokens = _tokenize(normalized)
|
|
69
|
+
if not tokens:
|
|
70
|
+
return []
|
|
71
|
+
|
|
72
|
+
scored: list[tuple[int, dict]] = []
|
|
73
|
+
for row in rows:
|
|
74
|
+
score = _score_row(row, normalized, tokens)
|
|
75
|
+
if score <= 0:
|
|
76
|
+
continue
|
|
77
|
+
scored.append((score, row))
|
|
78
|
+
|
|
79
|
+
scored.sort(
|
|
80
|
+
key=lambda item: (
|
|
81
|
+
-item[0],
|
|
82
|
+
str(item[1].get("dataset_alias", "")),
|
|
83
|
+
str(item[1].get("field_name", "")),
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
return [row for _, row in scored[: max(limit, 0)]]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _tokenize(value: str) -> list[str]:
|
|
91
|
+
"""将搜索词切分为 token。"""
|
|
92
|
+
return [token for token in re.split(r"[\s_\-./]+", value.lower()) if token]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _score_row(row: dict, keyword: str, tokens: list[str]) -> int:
|
|
96
|
+
"""为单行结果打分。"""
|
|
97
|
+
field_name = str(row.get("field_name", "")).lower()
|
|
98
|
+
verbose_name = str(row.get("verbose_name", "")).lower()
|
|
99
|
+
keywords = str(row.get("keywords", "")).lower()
|
|
100
|
+
description = str(row.get("description", "")).lower()
|
|
101
|
+
dataset_alias = str(row.get("dataset_alias", "")).lower()
|
|
102
|
+
dataset_name = str(row.get("dataset_name", "")).lower()
|
|
103
|
+
fulltext = " ".join(str(value).lower() for value in row.values())
|
|
104
|
+
|
|
105
|
+
score = 0
|
|
106
|
+
|
|
107
|
+
# 精确/短字段优先
|
|
108
|
+
if keyword == field_name:
|
|
109
|
+
score += 120
|
|
110
|
+
if keyword == verbose_name:
|
|
111
|
+
score += 100
|
|
112
|
+
if keyword == dataset_alias:
|
|
113
|
+
score += 40
|
|
114
|
+
|
|
115
|
+
# 连续子串匹配
|
|
116
|
+
if keyword in field_name:
|
|
117
|
+
score += 60
|
|
118
|
+
if keyword in verbose_name:
|
|
119
|
+
score += 45
|
|
120
|
+
if keyword in keywords:
|
|
121
|
+
score += 35
|
|
122
|
+
if keyword in dataset_name:
|
|
123
|
+
score += 20
|
|
124
|
+
if keyword in description:
|
|
125
|
+
score += 10
|
|
126
|
+
|
|
127
|
+
# token 逐项匹配
|
|
128
|
+
score += _token_match_score(field_name, tokens, 16)
|
|
129
|
+
score += _token_match_score(verbose_name, tokens, 12)
|
|
130
|
+
score += _token_match_score(keywords, tokens, 9)
|
|
131
|
+
score += _token_match_score(dataset_name, tokens, 6)
|
|
132
|
+
score += _token_match_score(description, tokens, 4)
|
|
133
|
+
score += _token_match_score(fulltext, tokens, 2)
|
|
134
|
+
|
|
135
|
+
return score
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _token_match_score(text: str, tokens: list[str], weight: int) -> int:
|
|
139
|
+
"""计算 token 命中的加权分数。"""
|
|
140
|
+
return sum(weight for token in tokens if token in text)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""ops-dataset-query Skill 的查询转发脚本。
|
|
2
|
+
|
|
3
|
+
该脚本不直接访问后端接口,只负责把查询动作转交给 `opscli query`。
|
|
4
|
+
适合在 Skill 目录内直接运行:
|
|
5
|
+
|
|
6
|
+
- `python query.py metadata --dataset ds_xxx`
|
|
7
|
+
- `python query.py run --payload payload.json`
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import importlib.util
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
import shutil
|
|
17
|
+
import subprocess
|
|
18
|
+
import sys
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def build_opscli_prefix() -> list[str]:
|
|
22
|
+
"""解析 opscli 调用前缀。
|
|
23
|
+
|
|
24
|
+
优先级:
|
|
25
|
+
1. 环境变量 OPSCLI_BIN
|
|
26
|
+
2. PATH 中的 opscli 可执行文件
|
|
27
|
+
3. 当前 Python 环境可导入 opscli.cli 时使用 `python -m opscli.cli`
|
|
28
|
+
"""
|
|
29
|
+
env_bin = os.getenv("OPSCLI_BIN")
|
|
30
|
+
if env_bin:
|
|
31
|
+
return [env_bin]
|
|
32
|
+
|
|
33
|
+
cli_bin = shutil.which("opscli")
|
|
34
|
+
if cli_bin:
|
|
35
|
+
return [cli_bin]
|
|
36
|
+
|
|
37
|
+
if importlib.util.find_spec("opscli.cli") is not None:
|
|
38
|
+
return [sys.executable, "-m", "opscli.cli"]
|
|
39
|
+
|
|
40
|
+
raise RuntimeError("未找到 opscli,请先安装 opscli,或通过 OPSCLI_BIN 指定可执行路径")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def build_command(args: argparse.Namespace) -> list[str]:
|
|
44
|
+
"""根据脚本参数构造最终的 opscli query 命令。"""
|
|
45
|
+
command = [*build_opscli_prefix(), "query", args.command]
|
|
46
|
+
|
|
47
|
+
if args.command == "metadata":
|
|
48
|
+
if args.dataset:
|
|
49
|
+
command.extend(["--dataset", args.dataset])
|
|
50
|
+
if args.table_id is not None:
|
|
51
|
+
command.extend(["--table-id", str(args.table_id)])
|
|
52
|
+
if args.skills_dir:
|
|
53
|
+
command.extend(["--skills-dir", args.skills_dir])
|
|
54
|
+
elif args.command == "run":
|
|
55
|
+
command.extend(["--payload", args.payload])
|
|
56
|
+
elif args.command == "build":
|
|
57
|
+
if args.dataset:
|
|
58
|
+
command.extend(["--dataset", args.dataset])
|
|
59
|
+
if args.table_id is not None:
|
|
60
|
+
command.extend(["--table-id", str(args.table_id)])
|
|
61
|
+
for item in args.dimension or []:
|
|
62
|
+
command.extend(["--dimension", item])
|
|
63
|
+
for item in args.metric or []:
|
|
64
|
+
command.extend(["--metric", item])
|
|
65
|
+
for item in args.where or []:
|
|
66
|
+
command.extend(["--where", item])
|
|
67
|
+
for item in args.order_by or []:
|
|
68
|
+
command.extend(["--order-by", item])
|
|
69
|
+
if args.where_json:
|
|
70
|
+
command.extend(["--where-json", args.where_json])
|
|
71
|
+
if args.where_file:
|
|
72
|
+
command.extend(["--where-file", args.where_file])
|
|
73
|
+
if args.skills_dir:
|
|
74
|
+
command.extend(["--skills-dir", args.skills_dir])
|
|
75
|
+
if args.output:
|
|
76
|
+
command.extend(["--output", args.output])
|
|
77
|
+
command.extend(["--limit", str(args.limit), "--offset", str(args.offset)])
|
|
78
|
+
if args.run:
|
|
79
|
+
command.append("--run")
|
|
80
|
+
|
|
81
|
+
if args.pretty:
|
|
82
|
+
command.append("--pretty")
|
|
83
|
+
return command
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _emit_error(message: str) -> None:
|
|
87
|
+
"""以 JSON 形式输出本地错误,保持脚本行为稳定。"""
|
|
88
|
+
print(
|
|
89
|
+
json.dumps(
|
|
90
|
+
{
|
|
91
|
+
"success": False,
|
|
92
|
+
"command": "ops-dataset-query query",
|
|
93
|
+
"data": None,
|
|
94
|
+
"error": message,
|
|
95
|
+
},
|
|
96
|
+
ensure_ascii=False,
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def main() -> None:
|
|
102
|
+
"""运行查询转发脚本。"""
|
|
103
|
+
parser = argparse.ArgumentParser()
|
|
104
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
105
|
+
|
|
106
|
+
metadata = subparsers.add_parser("metadata")
|
|
107
|
+
metadata.add_argument("--dataset", help="dataset_alias")
|
|
108
|
+
metadata.add_argument("--table-id", type=int, help="table_id")
|
|
109
|
+
metadata.add_argument("--skills-dir", help="指定 Skill 目录")
|
|
110
|
+
metadata.add_argument("--pretty", action="store_true", help="格式化输出")
|
|
111
|
+
|
|
112
|
+
run = subparsers.add_parser("run")
|
|
113
|
+
run.add_argument("--payload", required=True, help="查询 payload JSON 文件")
|
|
114
|
+
run.add_argument("--pretty", action="store_true", help="格式化输出")
|
|
115
|
+
|
|
116
|
+
build = subparsers.add_parser("build")
|
|
117
|
+
build.add_argument("--dataset", help="dataset_alias")
|
|
118
|
+
build.add_argument("--table-id", type=int, help="table_id")
|
|
119
|
+
build.add_argument("--dimension", action="append", help="维度定义:field_name[:alias]")
|
|
120
|
+
build.add_argument("--metric", action="append", help="指标定义:field_name:aggregation[:alias]")
|
|
121
|
+
build.add_argument("--where", action="append", help="筛选条件:field|operator|value_json,可重复")
|
|
122
|
+
build.add_argument("--where-json", help="where JSON 字符串")
|
|
123
|
+
build.add_argument("--where-file", help="where JSON 文件路径")
|
|
124
|
+
build.add_argument("--order-by", action="append", help="排序定义:expr[:asc|desc]")
|
|
125
|
+
build.add_argument("--limit", type=int, default=20, help="limit,默认 20")
|
|
126
|
+
build.add_argument("--offset", type=int, default=0, help="offset,默认 0")
|
|
127
|
+
build.add_argument("--skills-dir", help="指定 Skill 目录")
|
|
128
|
+
build.add_argument("--output", help="将 payload 写入指定文件")
|
|
129
|
+
build.add_argument("--run", action="store_true", help="构造后立即执行查询")
|
|
130
|
+
build.add_argument("--pretty", action="store_true", help="格式化输出")
|
|
131
|
+
|
|
132
|
+
args = parser.parse_args()
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
command = build_command(args)
|
|
136
|
+
except Exception as exc:
|
|
137
|
+
_emit_error(str(exc))
|
|
138
|
+
raise SystemExit(1) from exc
|
|
139
|
+
|
|
140
|
+
result = subprocess.run(command, check=False)
|
|
141
|
+
raise SystemExit(result.returncode)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
if __name__ == "__main__":
|
|
145
|
+
main()
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""ops-dataset-query Skill 的搜索脚本。
|
|
2
|
+
|
|
3
|
+
提供命令行关键词搜索功能,支持:
|
|
4
|
+
- 关键词搜索
|
|
5
|
+
- 指定数据集过滤
|
|
6
|
+
- 限制返回数量
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import json
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from core import filter_rows_by_dataset, load_csv_rows, search_rows
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main() -> None:
|
|
19
|
+
"""读取 CSV 数据并按关键词搜索,输出匹配结果。"""
|
|
20
|
+
parser = argparse.ArgumentParser()
|
|
21
|
+
parser.add_argument("keyword")
|
|
22
|
+
parser.add_argument("--dataset", help="按 dataset_alias 过滤")
|
|
23
|
+
parser.add_argument("-n", "--limit", type=int, default=10, help="限制返回条数,默认 10")
|
|
24
|
+
args = parser.parse_args()
|
|
25
|
+
|
|
26
|
+
# 数据目录位于脚本上级目录的 data/ 子目录
|
|
27
|
+
data_dir = Path(__file__).resolve().parent.parent / "data"
|
|
28
|
+
rows = load_csv_rows(data_dir / "dataset_fields.csv")
|
|
29
|
+
filtered_rows = filter_rows_by_dataset(rows, args.dataset)
|
|
30
|
+
matched_rows = search_rows(filtered_rows, args.keyword, limit=args.limit)
|
|
31
|
+
|
|
32
|
+
print(json.dumps(matched_rows, ensure_ascii=False, indent=2))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
if __name__ == "__main__":
|
|
36
|
+
main()
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""ops-dataset-query Skill 的本地更新脚本。
|
|
2
|
+
|
|
3
|
+
提供独立于 `opscli skills upgrade` 的本地入口,适合在 Skill 目录内直接运行:
|
|
4
|
+
|
|
5
|
+
- `python updater.py --check`:仅检查远端版本
|
|
6
|
+
- `python updater.py`:执行更新
|
|
7
|
+
- `python updater.py --force`:强制重新拉取
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import json
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from opscli.skills.models import SkillRecord
|
|
17
|
+
from opscli.skills.updater import SkillsUpdater
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def build_record(skill_root: Path) -> SkillRecord:
|
|
21
|
+
"""基于当前 Skill 目录构造记录对象。"""
|
|
22
|
+
data_dir = skill_root / "data"
|
|
23
|
+
version_file = data_dir / "VERSION.json"
|
|
24
|
+
version = "v0.0.0"
|
|
25
|
+
if version_file.exists():
|
|
26
|
+
payload = json.loads(version_file.read_text(encoding="utf-8"))
|
|
27
|
+
version = str(payload.get("version", "v0.0.0"))
|
|
28
|
+
|
|
29
|
+
return SkillRecord(
|
|
30
|
+
name="ops-dataset-query",
|
|
31
|
+
version=version,
|
|
32
|
+
runtime="custom",
|
|
33
|
+
root=skill_root,
|
|
34
|
+
version_file=version_file,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def check_update(updater: SkillsUpdater, record: SkillRecord) -> dict:
|
|
39
|
+
"""检查当前 Skill 是否存在远端更新。"""
|
|
40
|
+
manifest = updater.fetch_manifest(record.name)
|
|
41
|
+
if not manifest:
|
|
42
|
+
raise ValueError("远端 manifest 不存在")
|
|
43
|
+
|
|
44
|
+
remote_version = str(manifest.get("version", "v0.0.0"))
|
|
45
|
+
compare_result = updater.compare_versions(record.version, remote_version)
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
"name": record.name,
|
|
49
|
+
"current_version": record.version,
|
|
50
|
+
"remote_version": remote_version,
|
|
51
|
+
"update_available": compare_result < 0,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _emit(payload: dict, pretty: bool) -> None:
|
|
56
|
+
"""统一输出 JSON。"""
|
|
57
|
+
if pretty:
|
|
58
|
+
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
59
|
+
else:
|
|
60
|
+
print(json.dumps(payload, ensure_ascii=False))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def main() -> None:
|
|
64
|
+
"""运行本地更新脚本。"""
|
|
65
|
+
parser = argparse.ArgumentParser()
|
|
66
|
+
parser.add_argument("--check", action="store_true", help="仅检查远端版本,不执行更新")
|
|
67
|
+
parser.add_argument("--force", action="store_true", help="强制重新下载远端数据")
|
|
68
|
+
parser.add_argument("--pretty", action="store_true", help="格式化输出 JSON")
|
|
69
|
+
args = parser.parse_args()
|
|
70
|
+
|
|
71
|
+
skill_root = Path(__file__).resolve().parent.parent
|
|
72
|
+
updater = SkillsUpdater()
|
|
73
|
+
record = build_record(skill_root)
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
if args.check:
|
|
77
|
+
payload = {
|
|
78
|
+
"success": True,
|
|
79
|
+
"command": "ops-dataset-query updater check",
|
|
80
|
+
"data": check_update(updater, record),
|
|
81
|
+
"error": None,
|
|
82
|
+
}
|
|
83
|
+
else:
|
|
84
|
+
result = updater.upgrade_ops_dataset_query(record, force=args.force)
|
|
85
|
+
payload = {
|
|
86
|
+
"success": True,
|
|
87
|
+
"command": "ops-dataset-query updater upgrade",
|
|
88
|
+
"data": result.to_dict(),
|
|
89
|
+
"error": None,
|
|
90
|
+
}
|
|
91
|
+
_emit(payload, args.pretty)
|
|
92
|
+
except Exception as exc:
|
|
93
|
+
_emit(
|
|
94
|
+
{
|
|
95
|
+
"success": False,
|
|
96
|
+
"command": "ops-dataset-query updater",
|
|
97
|
+
"data": None,
|
|
98
|
+
"error": str(exc),
|
|
99
|
+
},
|
|
100
|
+
args.pretty,
|
|
101
|
+
)
|
|
102
|
+
raise SystemExit(1) from exc
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
if __name__ == "__main__":
|
|
106
|
+
main()
|