amap-cli 0.1.0__tar.gz → 0.1.2__tar.gz
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.
- {amap_cli-0.1.0 → amap_cli-0.1.2}/PKG-INFO +38 -2
- {amap_cli-0.1.0 → amap_cli-0.1.2}/README.md +37 -1
- {amap_cli-0.1.0 → amap_cli-0.1.2}/pyproject.toml +4 -1
- {amap_cli-0.1.0 → amap_cli-0.1.2}/src/amap_cli/cli.py +2 -0
- {amap_cli-0.1.0 → amap_cli-0.1.2}/src/amap_cli/config.py +69 -11
- amap_cli-0.1.2/src/amap_cli/skill.py +102 -0
- {amap_cli-0.1.0 → amap_cli-0.1.2}/uv.lock +1 -1
- {amap_cli-0.1.0 → amap_cli-0.1.2}/.gitignore +0 -0
- {amap_cli-0.1.0 → amap_cli-0.1.2}/.trae/specs/build-amap-cli/checklist.md +0 -0
- {amap_cli-0.1.0 → amap_cli-0.1.2}/.trae/specs/build-amap-cli/spec.md +0 -0
- {amap_cli-0.1.0 → amap_cli-0.1.2}/.trae/specs/build-amap-cli/tasks.md +0 -0
- {amap_cli-0.1.0 → amap_cli-0.1.2}/amap-gui-reference.md +0 -0
- {amap_cli-0.1.0 → amap_cli-0.1.2}/docs/manual-verification.md +0 -0
- {amap_cli-0.1.0 → amap_cli-0.1.2}/skills/amap-cli/SKILL.md +0 -0
- {amap_cli-0.1.0 → amap_cli-0.1.2}/src/amap_cli/__init__.py +0 -0
- {amap_cli-0.1.0 → amap_cli-0.1.2}/src/amap_cli/__main__.py +0 -0
- {amap_cli-0.1.0 → amap_cli-0.1.2}/src/amap_cli/api.py +0 -0
- {amap_cli-0.1.0 → amap_cli-0.1.2}/src/amap_cli/distance.py +0 -0
- {amap_cli-0.1.0 → amap_cli-0.1.2}/src/amap_cli/errors.py +0 -0
- {amap_cli-0.1.0 → amap_cli-0.1.2}/src/amap_cli/geocode.py +0 -0
- {amap_cli-0.1.0 → amap_cli-0.1.2}/src/amap_cli/output.py +0 -0
- {amap_cli-0.1.0 → amap_cli-0.1.2}/src/amap_cli/params.py +0 -0
- {amap_cli-0.1.0 → amap_cli-0.1.2}/src/amap_cli/route.py +0 -0
- {amap_cli-0.1.0 → amap_cli-0.1.2}/src/amap_cli/search_poi.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: amap-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Pure command-line CLI for Amap APIs.
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -94,6 +94,24 @@ amap-cli search-poi --keyword 星巴克 --city 北京
|
|
|
94
94
|
- `distance` 命令在起终点都为坐标时可直接本地计算,不依赖 API Key
|
|
95
95
|
- 建议 Agent 直接解析命令返回的 JSON 结果
|
|
96
96
|
|
|
97
|
+
如果需要将内置 skill 安装到指定目录,例如 Agent 的 skills 根目录,可以执行:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
amap-cli install-skill --dir ~/.agents/skills
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
执行后会在目标目录下生成:
|
|
104
|
+
|
|
105
|
+
```text
|
|
106
|
+
~/.agents/skills/amap-cli/SKILL.md
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
如果目标目录中已存在同名 skill,可追加 `--force` 覆盖:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
amap-cli install-skill --dir ~/.agents/skills --force
|
|
113
|
+
```
|
|
114
|
+
|
|
97
115
|
## 命令说明
|
|
98
116
|
|
|
99
117
|
### 配置
|
|
@@ -116,6 +134,24 @@ amap-cli config show
|
|
|
116
134
|
~/Library/Application Support/amap-cli/config.json
|
|
117
135
|
```
|
|
118
136
|
|
|
137
|
+
### 安装 Skill
|
|
138
|
+
|
|
139
|
+
基础格式:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
amap-cli install-skill --dir <skills目录>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
可选参数:
|
|
146
|
+
|
|
147
|
+
- `--force`
|
|
148
|
+
|
|
149
|
+
说明:
|
|
150
|
+
|
|
151
|
+
- 会将内置的 `amap-cli` skill 安装到目标目录下的 `amap-cli/` 子目录
|
|
152
|
+
- 当目标目录不存在时会自动创建
|
|
153
|
+
- 当目标目录中已存在同名 skill 时,默认报错;追加 `--force` 后会覆盖
|
|
154
|
+
|
|
119
155
|
### 路径规划
|
|
120
156
|
|
|
121
157
|
基础格式:
|
|
@@ -268,7 +304,7 @@ uv publish \
|
|
|
268
304
|
如果只是验证包能否被安装,可以执行:
|
|
269
305
|
|
|
270
306
|
```bash
|
|
271
|
-
uvx --index
|
|
307
|
+
uvx --index https://test.pypi.org/simple/ amap-cli --help
|
|
272
308
|
```
|
|
273
309
|
|
|
274
310
|
### 5. 发布到正式 PyPI
|
|
@@ -87,6 +87,24 @@ amap-cli search-poi --keyword 星巴克 --city 北京
|
|
|
87
87
|
- `distance` 命令在起终点都为坐标时可直接本地计算,不依赖 API Key
|
|
88
88
|
- 建议 Agent 直接解析命令返回的 JSON 结果
|
|
89
89
|
|
|
90
|
+
如果需要将内置 skill 安装到指定目录,例如 Agent 的 skills 根目录,可以执行:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
amap-cli install-skill --dir ~/.agents/skills
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
执行后会在目标目录下生成:
|
|
97
|
+
|
|
98
|
+
```text
|
|
99
|
+
~/.agents/skills/amap-cli/SKILL.md
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
如果目标目录中已存在同名 skill,可追加 `--force` 覆盖:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
amap-cli install-skill --dir ~/.agents/skills --force
|
|
106
|
+
```
|
|
107
|
+
|
|
90
108
|
## 命令说明
|
|
91
109
|
|
|
92
110
|
### 配置
|
|
@@ -109,6 +127,24 @@ amap-cli config show
|
|
|
109
127
|
~/Library/Application Support/amap-cli/config.json
|
|
110
128
|
```
|
|
111
129
|
|
|
130
|
+
### 安装 Skill
|
|
131
|
+
|
|
132
|
+
基础格式:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
amap-cli install-skill --dir <skills目录>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
可选参数:
|
|
139
|
+
|
|
140
|
+
- `--force`
|
|
141
|
+
|
|
142
|
+
说明:
|
|
143
|
+
|
|
144
|
+
- 会将内置的 `amap-cli` skill 安装到目标目录下的 `amap-cli/` 子目录
|
|
145
|
+
- 当目标目录不存在时会自动创建
|
|
146
|
+
- 当目标目录中已存在同名 skill 时,默认报错;追加 `--force` 后会覆盖
|
|
147
|
+
|
|
112
148
|
### 路径规划
|
|
113
149
|
|
|
114
150
|
基础格式:
|
|
@@ -261,7 +297,7 @@ uv publish \
|
|
|
261
297
|
如果只是验证包能否被安装,可以执行:
|
|
262
298
|
|
|
263
299
|
```bash
|
|
264
|
-
uvx --index
|
|
300
|
+
uvx --index https://test.pypi.org/simple/ amap-cli --help
|
|
265
301
|
```
|
|
266
302
|
|
|
267
303
|
### 5. 发布到正式 PyPI
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "amap-cli"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "Pure command-line CLI for Amap APIs."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -16,5 +16,8 @@ amap-cli = "amap_cli.cli:main"
|
|
|
16
16
|
[tool.hatch.build.targets.wheel]
|
|
17
17
|
packages = ["src/amap_cli"]
|
|
18
18
|
|
|
19
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
20
|
+
"skills" = "amap_cli/skills"
|
|
21
|
+
|
|
19
22
|
[tool.uv]
|
|
20
23
|
package = true
|
|
@@ -11,6 +11,7 @@ from amap_cli.errors import AmapCliError, ValidationError
|
|
|
11
11
|
from amap_cli.output import emit_error, emit_success
|
|
12
12
|
from amap_cli.route import register_route_command
|
|
13
13
|
from amap_cli.search_poi import register_search_poi_command
|
|
14
|
+
from amap_cli.skill import register_install_skill_command
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class JsonArgumentParser(argparse.ArgumentParser):
|
|
@@ -29,6 +30,7 @@ def build_parser() -> JsonArgumentParser:
|
|
|
29
30
|
subparsers = parser.add_subparsers(dest="command")
|
|
30
31
|
|
|
31
32
|
_register_config_command(subparsers)
|
|
33
|
+
register_install_skill_command(subparsers)
|
|
32
34
|
register_distance_command(subparsers)
|
|
33
35
|
register_route_command(subparsers)
|
|
34
36
|
register_search_poi_command(subparsers)
|
|
@@ -81,27 +81,67 @@ def get_config_path() -> Path:
|
|
|
81
81
|
return get_config_dir() / CONFIG_FILE_NAME
|
|
82
82
|
|
|
83
83
|
|
|
84
|
+
def _merge_error_details(
|
|
85
|
+
current: Any | None,
|
|
86
|
+
*,
|
|
87
|
+
path: Path,
|
|
88
|
+
operation: str | None = None,
|
|
89
|
+
exc: BaseException | None = None,
|
|
90
|
+
) -> dict[str, Any]:
|
|
91
|
+
"""Attach config file context to an error payload."""
|
|
92
|
+
details = current.copy() if isinstance(current, dict) else {}
|
|
93
|
+
details["path"] = str(path)
|
|
94
|
+
if operation is not None:
|
|
95
|
+
details["operation"] = operation
|
|
96
|
+
if exc is not None:
|
|
97
|
+
details["type"] = type(exc).__name__
|
|
98
|
+
details["reason"] = str(exc)
|
|
99
|
+
return details
|
|
100
|
+
|
|
101
|
+
|
|
84
102
|
def load_config(*, required: bool = False) -> AmapConfig | None:
|
|
85
103
|
"""Load config from disk, optionally requiring it to exist."""
|
|
86
104
|
path = get_config_path()
|
|
87
|
-
|
|
105
|
+
try:
|
|
106
|
+
exists = path.exists()
|
|
107
|
+
except OSError as exc:
|
|
108
|
+
raise ConfigError(
|
|
109
|
+
"检查配置文件状态失败。",
|
|
110
|
+
details=_merge_error_details(None, path=path, operation="stat", exc=exc),
|
|
111
|
+
) from exc
|
|
112
|
+
|
|
113
|
+
if not exists:
|
|
88
114
|
if required:
|
|
89
115
|
raise MissingConfigError(
|
|
90
|
-
"未找到高德 API 配置,请先执行 `amap-cli config set --api-key <KEY>`。"
|
|
116
|
+
"未找到高德 API 配置,请先执行 `amap-cli config set --api-key <KEY>`。",
|
|
117
|
+
details=_merge_error_details(None, path=path),
|
|
91
118
|
)
|
|
92
119
|
return None
|
|
93
120
|
|
|
94
121
|
try:
|
|
95
122
|
raw = json.loads(path.read_text(encoding="utf-8"))
|
|
96
123
|
except json.JSONDecodeError as exc:
|
|
97
|
-
raise ConfigError(
|
|
124
|
+
raise ConfigError(
|
|
125
|
+
"配置文件不是合法的 JSON。",
|
|
126
|
+
details=_merge_error_details(None, path=path, operation="read", exc=exc),
|
|
127
|
+
) from exc
|
|
98
128
|
except OSError as exc:
|
|
99
|
-
raise ConfigError(
|
|
129
|
+
raise ConfigError(
|
|
130
|
+
"读取配置文件失败。",
|
|
131
|
+
details=_merge_error_details(None, path=path, operation="read", exc=exc),
|
|
132
|
+
) from exc
|
|
100
133
|
|
|
101
134
|
if not isinstance(raw, dict):
|
|
102
|
-
raise ConfigError(
|
|
135
|
+
raise ConfigError(
|
|
136
|
+
"配置文件内容格式无效。",
|
|
137
|
+
details=_merge_error_details(None, path=path),
|
|
138
|
+
)
|
|
103
139
|
|
|
104
|
-
|
|
140
|
+
try:
|
|
141
|
+
return AmapConfig.from_dict(raw)
|
|
142
|
+
except ConfigError as exc:
|
|
143
|
+
exc.details = _merge_error_details(exc.details, path=path)
|
|
144
|
+
raise
|
|
105
145
|
|
|
106
146
|
|
|
107
147
|
def save_config(
|
|
@@ -131,11 +171,29 @@ def save_config(
|
|
|
131
171
|
config.timeout_seconds = float(timeout_seconds)
|
|
132
172
|
|
|
133
173
|
path = get_config_path()
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
174
|
+
try:
|
|
175
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
176
|
+
except OSError as exc:
|
|
177
|
+
raise ConfigError(
|
|
178
|
+
"创建配置目录失败。",
|
|
179
|
+
details=_merge_error_details(
|
|
180
|
+
None,
|
|
181
|
+
path=path.parent,
|
|
182
|
+
operation="create_dir",
|
|
183
|
+
exc=exc,
|
|
184
|
+
),
|
|
185
|
+
) from exc
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
path.write_text(
|
|
189
|
+
json.dumps(config.to_dict(), ensure_ascii=False, indent=2) + "\n",
|
|
190
|
+
encoding="utf-8",
|
|
191
|
+
)
|
|
192
|
+
except OSError as exc:
|
|
193
|
+
raise ConfigError(
|
|
194
|
+
"写入配置文件失败。",
|
|
195
|
+
details=_merge_error_details(None, path=path, operation="write", exc=exc),
|
|
196
|
+
) from exc
|
|
139
197
|
|
|
140
198
|
if os.name != "nt":
|
|
141
199
|
try:
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Skill installation commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import shutil
|
|
7
|
+
from importlib import resources
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from amap_cli.errors import AmapCliError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def register_install_skill_command(
|
|
15
|
+
subparsers: argparse._SubParsersAction[argparse.ArgumentParser],
|
|
16
|
+
) -> None:
|
|
17
|
+
"""Register the bundled skill installation command."""
|
|
18
|
+
parser = subparsers.add_parser(
|
|
19
|
+
"install-skill",
|
|
20
|
+
help="安装内置 skill 到指定目录",
|
|
21
|
+
)
|
|
22
|
+
parser.add_argument(
|
|
23
|
+
"--dir",
|
|
24
|
+
dest="target_dir",
|
|
25
|
+
required=True,
|
|
26
|
+
help="skill 安装根目录,例如 ~/.agents/skills",
|
|
27
|
+
)
|
|
28
|
+
parser.add_argument(
|
|
29
|
+
"--force",
|
|
30
|
+
action="store_true",
|
|
31
|
+
help="目标 skill 已存在时覆盖",
|
|
32
|
+
)
|
|
33
|
+
parser.set_defaults(handler=_handle_install_skill)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _handle_install_skill(args: argparse.Namespace) -> dict[str, Any]:
|
|
37
|
+
"""Install the bundled skill into the requested directory."""
|
|
38
|
+
skill_name = "amap-cli"
|
|
39
|
+
source_dir = _resolve_bundled_skill_dir()
|
|
40
|
+
target_root = Path(args.target_dir).expanduser().resolve()
|
|
41
|
+
target_skill_dir = target_root / skill_name
|
|
42
|
+
|
|
43
|
+
overwritten = False
|
|
44
|
+
if target_skill_dir.exists():
|
|
45
|
+
if not args.force:
|
|
46
|
+
raise AmapCliError(
|
|
47
|
+
f"目标目录已存在 skill:{target_skill_dir}",
|
|
48
|
+
code="SKILL_ALREADY_EXISTS",
|
|
49
|
+
details={
|
|
50
|
+
"skill_path": str(target_skill_dir),
|
|
51
|
+
"hint": "如需覆盖,请追加 --force。",
|
|
52
|
+
},
|
|
53
|
+
exit_code=1,
|
|
54
|
+
)
|
|
55
|
+
if target_skill_dir.is_dir():
|
|
56
|
+
shutil.rmtree(target_skill_dir)
|
|
57
|
+
else:
|
|
58
|
+
target_skill_dir.unlink()
|
|
59
|
+
overwritten = True
|
|
60
|
+
|
|
61
|
+
target_root.mkdir(parents=True, exist_ok=True)
|
|
62
|
+
_copy_tree(source_dir, target_skill_dir)
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
"message": "skill 安装完成。",
|
|
66
|
+
"skill_name": skill_name,
|
|
67
|
+
"source": str(source_dir),
|
|
68
|
+
"target_dir": str(target_root),
|
|
69
|
+
"skill_path": str(target_skill_dir),
|
|
70
|
+
"overwritten": overwritten,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _resolve_bundled_skill_dir() -> Any:
|
|
75
|
+
"""Locate the bundled skill directory in installed or source layouts."""
|
|
76
|
+
packaged_skill_dir = resources.files("amap_cli").joinpath("skills", "amap-cli")
|
|
77
|
+
packaged_skill_file = packaged_skill_dir.joinpath("SKILL.md")
|
|
78
|
+
if packaged_skill_file.is_file():
|
|
79
|
+
return packaged_skill_dir
|
|
80
|
+
|
|
81
|
+
repo_skill_dir = Path(__file__).resolve().parents[2] / "skills" / "amap-cli"
|
|
82
|
+
if repo_skill_dir.is_dir():
|
|
83
|
+
return repo_skill_dir
|
|
84
|
+
|
|
85
|
+
raise AmapCliError(
|
|
86
|
+
"未找到内置 skill 资源。",
|
|
87
|
+
code="SKILL_RESOURCE_NOT_FOUND",
|
|
88
|
+
details={"skill_name": "amap-cli"},
|
|
89
|
+
exit_code=1,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _copy_tree(source_dir: Any, target_dir: Path) -> None:
|
|
94
|
+
"""Recursively copy the skill directory into the target path."""
|
|
95
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
96
|
+
for source_path in source_dir.iterdir():
|
|
97
|
+
target_path = target_dir / source_path.name
|
|
98
|
+
if source_path.is_dir():
|
|
99
|
+
_copy_tree(source_path, target_path)
|
|
100
|
+
continue
|
|
101
|
+
with source_path.open("rb") as source_file, target_path.open("wb") as target_file:
|
|
102
|
+
shutil.copyfileobj(source_file, target_file)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|