amap-cli 0.1.2__tar.gz → 0.1.4__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.
Files changed (25) hide show
  1. {amap_cli-0.1.2 → amap_cli-0.1.4}/PKG-INFO +55 -2
  2. {amap_cli-0.1.2 → amap_cli-0.1.4}/README.md +54 -1
  3. {amap_cli-0.1.2 → amap_cli-0.1.4}/docs/manual-verification.md +34 -7
  4. {amap_cli-0.1.2 → amap_cli-0.1.4}/pyproject.toml +1 -1
  5. {amap_cli-0.1.2 → amap_cli-0.1.4}/skills/amap-cli/SKILL.md +40 -3
  6. {amap_cli-0.1.2 → amap_cli-0.1.4}/src/amap_cli/api.py +29 -24
  7. {amap_cli-0.1.2 → amap_cli-0.1.4}/src/amap_cli/cli.py +10 -1
  8. {amap_cli-0.1.2 → amap_cli-0.1.4}/src/amap_cli/config.py +18 -0
  9. amap_cli-0.1.4/src/amap_cli/geocode_command.py +91 -0
  10. {amap_cli-0.1.2 → amap_cli-0.1.4}/uv.lock +1 -1
  11. {amap_cli-0.1.2 → amap_cli-0.1.4}/.gitignore +0 -0
  12. {amap_cli-0.1.2 → amap_cli-0.1.4}/.trae/specs/build-amap-cli/checklist.md +0 -0
  13. {amap_cli-0.1.2 → amap_cli-0.1.4}/.trae/specs/build-amap-cli/spec.md +0 -0
  14. {amap_cli-0.1.2 → amap_cli-0.1.4}/.trae/specs/build-amap-cli/tasks.md +0 -0
  15. {amap_cli-0.1.2 → amap_cli-0.1.4}/amap-gui-reference.md +0 -0
  16. {amap_cli-0.1.2 → amap_cli-0.1.4}/src/amap_cli/__init__.py +0 -0
  17. {amap_cli-0.1.2 → amap_cli-0.1.4}/src/amap_cli/__main__.py +0 -0
  18. {amap_cli-0.1.2 → amap_cli-0.1.4}/src/amap_cli/distance.py +0 -0
  19. {amap_cli-0.1.2 → amap_cli-0.1.4}/src/amap_cli/errors.py +0 -0
  20. {amap_cli-0.1.2 → amap_cli-0.1.4}/src/amap_cli/geocode.py +0 -0
  21. {amap_cli-0.1.2 → amap_cli-0.1.4}/src/amap_cli/output.py +0 -0
  22. {amap_cli-0.1.2 → amap_cli-0.1.4}/src/amap_cli/params.py +0 -0
  23. {amap_cli-0.1.2 → amap_cli-0.1.4}/src/amap_cli/route.py +0 -0
  24. {amap_cli-0.1.2 → amap_cli-0.1.4}/src/amap_cli/search_poi.py +0 -0
  25. {amap_cli-0.1.2 → amap_cli-0.1.4}/src/amap_cli/skill.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: amap-cli
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: Pure command-line CLI for Amap APIs.
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -13,6 +13,7 @@ Description-Content-Type: text/markdown
13
13
 
14
14
  - 纯命令行调用高德地图 API
15
15
  - 支持命令行写入和读取高德 API Key
16
+ - 支持地理编码
16
17
  - 支持两点直线距离计算
17
18
  - 支持路径规划
18
19
  - 支持 POI 搜索
@@ -35,6 +36,19 @@ uvx amap-cli --help
35
36
  uvx amap-cli config set --api-key <YOUR_AMAP_KEY>
36
37
  ```
37
38
 
39
+ 高德免费账号默认限流较严,工具默认会在每次 API 调用结束后 sleep `0.34` 秒;如需调整或关闭,可配置:
40
+
41
+ ```bash
42
+ uvx amap-cli config set --request-sleep-seconds 0.34
43
+ uvx amap-cli config set --request-sleep-seconds 0
44
+ ```
45
+
46
+ 地理编码示例:
47
+
48
+ ```bash
49
+ uvx amap-cli geocode --address 北京南站
50
+ ```
51
+
38
52
  路径规划示例:
39
53
 
40
54
  ```bash
@@ -79,6 +93,7 @@ amap-cli --help
79
93
 
80
94
  ```bash
81
95
  amap-cli config set --api-key <YOUR_AMAP_KEY>
96
+ amap-cli geocode --address 北京南站
82
97
  amap-cli distance --from 116.397,39.909 --to 116.407,39.904
83
98
  amap-cli route --from 北京南站 --to 天安门 --type driving
84
99
  amap-cli search-poi --keyword 星巴克 --city 北京
@@ -90,7 +105,7 @@ amap-cli search-poi --keyword 星巴克 --city 北京
90
105
 
91
106
  - skill 的目标是让 Agent 直接通过 `uvx amap-cli` 调用本工具
92
107
  - 使用方式与上面的“直接使用 `uvx` 运行”一致
93
- - 使用前先配置高德 API Key,之后即可发起路径规划和 POI 搜索
108
+ - 使用前先配置高德 API Key,之后即可发起地理编码、路径规划和 POI 搜索
94
109
  - `distance` 命令在起终点都为坐标时可直接本地计算,不依赖 API Key
95
110
  - 建议 Agent 直接解析命令返回的 JSON 结果
96
111
 
@@ -122,6 +137,18 @@ amap-cli install-skill --dir ~/.agents/skills --force
122
137
  amap-cli config set --api-key <YOUR_AMAP_KEY>
123
138
  ```
124
139
 
140
+ 设置每次高德 API 调用结束后的 sleep 时间:
141
+
142
+ ```bash
143
+ amap-cli config set --request-sleep-seconds 0.34
144
+ ```
145
+
146
+ 关闭该 sleep:
147
+
148
+ ```bash
149
+ amap-cli config set --request-sleep-seconds 0
150
+ ```
151
+
125
152
  查看当前配置:
126
153
 
127
154
  ```bash
@@ -134,6 +161,12 @@ amap-cli config show
134
161
  ~/Library/Application Support/amap-cli/config.json
135
162
  ```
136
163
 
164
+ 说明:
165
+
166
+ - `request_sleep_seconds` 默认为 `0.34`
167
+ - 该配置会在每次真实的高德 API 调用结束后执行一次 sleep
168
+ - 设置为 `0` 表示关闭节流等待
169
+
137
170
  ### 安装 Skill
138
171
 
139
172
  基础格式:
@@ -152,6 +185,26 @@ amap-cli install-skill --dir <skills目录>
152
185
  - 当目标目录不存在时会自动创建
153
186
  - 当目标目录中已存在同名 skill 时,默认报错;追加 `--force` 后会覆盖
154
187
 
188
+ ### 地理编码
189
+
190
+ 基础格式:
191
+
192
+ ```bash
193
+ amap-cli geocode --address <结构化地址|地标名称>
194
+ ```
195
+
196
+ 可选参数:
197
+
198
+ - `--city`
199
+
200
+ 说明:
201
+
202
+ - 调用高德地理编码接口,将结构化地址或地标性名胜景区、建筑物名称解析为坐标
203
+ - 传入 `--city` 时,会优先在对应城市范围内解析
204
+ - 返回结果中的 `geocode.location` 为 `[经度, 纬度]`
205
+ - 返回结果中的 `geocode.formattedAddress` 为高德返回的标准化地址
206
+ - 对于较模糊的名称,建议补充更完整地址或增加 `--city`
207
+
155
208
  ### 路径规划
156
209
 
157
210
  基础格式:
@@ -6,6 +6,7 @@
6
6
 
7
7
  - 纯命令行调用高德地图 API
8
8
  - 支持命令行写入和读取高德 API Key
9
+ - 支持地理编码
9
10
  - 支持两点直线距离计算
10
11
  - 支持路径规划
11
12
  - 支持 POI 搜索
@@ -28,6 +29,19 @@ uvx amap-cli --help
28
29
  uvx amap-cli config set --api-key <YOUR_AMAP_KEY>
29
30
  ```
30
31
 
32
+ 高德免费账号默认限流较严,工具默认会在每次 API 调用结束后 sleep `0.34` 秒;如需调整或关闭,可配置:
33
+
34
+ ```bash
35
+ uvx amap-cli config set --request-sleep-seconds 0.34
36
+ uvx amap-cli config set --request-sleep-seconds 0
37
+ ```
38
+
39
+ 地理编码示例:
40
+
41
+ ```bash
42
+ uvx amap-cli geocode --address 北京南站
43
+ ```
44
+
31
45
  路径规划示例:
32
46
 
33
47
  ```bash
@@ -72,6 +86,7 @@ amap-cli --help
72
86
 
73
87
  ```bash
74
88
  amap-cli config set --api-key <YOUR_AMAP_KEY>
89
+ amap-cli geocode --address 北京南站
75
90
  amap-cli distance --from 116.397,39.909 --to 116.407,39.904
76
91
  amap-cli route --from 北京南站 --to 天安门 --type driving
77
92
  amap-cli search-poi --keyword 星巴克 --city 北京
@@ -83,7 +98,7 @@ amap-cli search-poi --keyword 星巴克 --city 北京
83
98
 
84
99
  - skill 的目标是让 Agent 直接通过 `uvx amap-cli` 调用本工具
85
100
  - 使用方式与上面的“直接使用 `uvx` 运行”一致
86
- - 使用前先配置高德 API Key,之后即可发起路径规划和 POI 搜索
101
+ - 使用前先配置高德 API Key,之后即可发起地理编码、路径规划和 POI 搜索
87
102
  - `distance` 命令在起终点都为坐标时可直接本地计算,不依赖 API Key
88
103
  - 建议 Agent 直接解析命令返回的 JSON 结果
89
104
 
@@ -115,6 +130,18 @@ amap-cli install-skill --dir ~/.agents/skills --force
115
130
  amap-cli config set --api-key <YOUR_AMAP_KEY>
116
131
  ```
117
132
 
133
+ 设置每次高德 API 调用结束后的 sleep 时间:
134
+
135
+ ```bash
136
+ amap-cli config set --request-sleep-seconds 0.34
137
+ ```
138
+
139
+ 关闭该 sleep:
140
+
141
+ ```bash
142
+ amap-cli config set --request-sleep-seconds 0
143
+ ```
144
+
118
145
  查看当前配置:
119
146
 
120
147
  ```bash
@@ -127,6 +154,12 @@ amap-cli config show
127
154
  ~/Library/Application Support/amap-cli/config.json
128
155
  ```
129
156
 
157
+ 说明:
158
+
159
+ - `request_sleep_seconds` 默认为 `0.34`
160
+ - 该配置会在每次真实的高德 API 调用结束后执行一次 sleep
161
+ - 设置为 `0` 表示关闭节流等待
162
+
130
163
  ### 安装 Skill
131
164
 
132
165
  基础格式:
@@ -145,6 +178,26 @@ amap-cli install-skill --dir <skills目录>
145
178
  - 当目标目录不存在时会自动创建
146
179
  - 当目标目录中已存在同名 skill 时,默认报错;追加 `--force` 后会覆盖
147
180
 
181
+ ### 地理编码
182
+
183
+ 基础格式:
184
+
185
+ ```bash
186
+ amap-cli geocode --address <结构化地址|地标名称>
187
+ ```
188
+
189
+ 可选参数:
190
+
191
+ - `--city`
192
+
193
+ 说明:
194
+
195
+ - 调用高德地理编码接口,将结构化地址或地标性名胜景区、建筑物名称解析为坐标
196
+ - 传入 `--city` 时,会优先在对应城市范围内解析
197
+ - 返回结果中的 `geocode.location` 为 `[经度, 纬度]`
198
+ - 返回结果中的 `geocode.formattedAddress` 为高德返回的标准化地址
199
+ - 对于较模糊的名称,建议补充更完整地址或增加 `--city`
200
+
148
201
  ### 路径规划
149
202
 
150
203
  基础格式:
@@ -24,10 +24,35 @@ uv run python -m amap_cli --help
24
24
  结果:
25
25
 
26
26
  - `uv sync` 成功完成本地安装
27
- - `amap-cli --help` 正确显示 `config`、`distance`、`route`、`search-poi` 四个子命令
27
+ - `amap-cli --help` 正确显示 `config`、`geocode`、`distance`、`route`、`search-poi` 五个子命令
28
28
  - `python -m amap_cli --help` 与脚本入口行为一致
29
29
 
30
- ### 2. `distance` 坐标输入走本地直线距离计算
30
+ ### 2. `geocode` 典型输入通过 CLI 主入口走通到 handler
31
+
32
+ 说明:
33
+
34
+ - 使用 `amap_cli.cli.main(...)` 作为 CLI 主入口
35
+ - 通过 monkeypatch 将 `amap_cli.geocode_command.AmapApiClient` 替换为假 client
36
+ - 仍然走真实参数解析、handler 与统一 JSON 输出
37
+
38
+ 验证输入:
39
+
40
+ ```bash
41
+ geocode --address 北京南站
42
+ ```
43
+
44
+ 假 client 返回:
45
+
46
+ - `/v3/geocode/geo`:北京南站的坐标与标准化地址
47
+
48
+ 结果:
49
+
50
+ - CLI 返回 `success=true`
51
+ - `state.address` 正确保留原始查询
52
+ - `geocode.location` 返回坐标数组
53
+ - `geocode.formattedAddress` 正确透传
54
+
55
+ ### 3. `distance` 坐标输入走本地直线距离计算
31
56
 
32
57
  执行:
33
58
 
@@ -42,13 +67,14 @@ uv run amap-cli distance --from 116.397,39.909 --to 116.407,39.904
42
67
  - `state.mode=straight_line`
43
68
  - `summary.distance` 返回两点之间的直线距离(米)
44
69
 
45
- ### 3. 配置写入与自动读取
70
+ ### 4. 配置写入与自动读取
46
71
 
47
72
  为避免污染真实用户配置,验证时使用隔离 `HOME`:
48
73
 
49
74
  ```bash
50
75
  TMP_HOME=$(mktemp -d)
51
76
  HOME="$TMP_HOME" uv run amap-cli config set --api-key test-demo-key --timeout-seconds 12
77
+ HOME="$TMP_HOME" uv run amap-cli config set --request-sleep-seconds 0
52
78
  HOME="$TMP_HOME" uv run amap-cli config show
53
79
  ```
54
80
 
@@ -58,8 +84,9 @@ HOME="$TMP_HOME" uv run amap-cli config show
58
84
  - macOS 下配置落在 `~/Library/Application Support/amap-cli/config.json`
59
85
  - 再次执行 `config show` 时可自动读取刚写入的配置
60
86
  - `api_key` 输出已脱敏
87
+ - `request_sleep_seconds` 可写入为 `0`,用于关闭默认的请求后 sleep
61
88
 
62
- ### 4. `route` 典型输入通过 CLI 主入口走通到 handler
89
+ ### 5. `route` 典型输入通过 CLI 主入口走通到 handler
63
90
 
64
91
  说明:
65
92
 
@@ -85,7 +112,7 @@ route --from 北京南站 --to 天安门 --type driving
85
112
  - 起终点地名与坐标正确进入输出
86
113
  - `summary.distance`、`summary.time`、`summary.steps` 正常生成
87
114
 
88
- ### 5. `search-poi` 典型输入通过 CLI 主入口走通到 handler
115
+ ### 6. `search-poi` 典型输入通过 CLI 主入口走通到 handler
89
116
 
90
117
  说明:
91
118
 
@@ -130,5 +157,5 @@ search-poi --keyword 星巴克 --city 北京 --pageSize 1
130
157
 
131
158
  结论:
132
159
 
133
- - 当前五项手动验证均已通过
134
- - CLI 主入口已完成 `distance`、`route`、`search-poi` 整合
160
+ - 当前六项手动验证均已通过
161
+ - CLI 主入口已完成 `geocode`、`distance`、`route`、`search-poi` 整合
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "amap-cli"
7
- version = "0.1.2"
7
+ version = "0.1.4"
8
8
  description = "Pure command-line CLI for Amap APIs."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,18 +1,20 @@
1
1
  ---
2
2
  name: amap-cli
3
- description: Use amap-cli for terminal-based Amap distance calculation, route planning, and POI search. Use when the user needs machine-readable map results in this project. Do not use for GUI-based map interaction.
3
+ description: Use amap-cli for terminal-based Amap geocoding, distance calculation, route planning, and POI search. Use when the user needs machine-readable map results in this project. Do not use for GUI-based map interaction.
4
4
  ---
5
5
 
6
6
  # Amap CLI
7
7
 
8
- 在需要高德地图直线距离计算、路径规划或 POI 搜索时,直接调用 `amap-cli`。
8
+ 在需要高德地图地理编码、直线距离计算、路径规划或 POI 搜索时,直接调用 `amap-cli`。
9
9
 
10
10
  ## 操作规则
11
11
 
12
12
  - 统一使用 `uvx amap-cli` 调用命令
13
13
  - 优先返回和解析 JSON 结果,不依赖自然语言输出
14
- - 在执行 `route` 或 `search-poi` 前,先确认高德 API Key 已配置
14
+ - 在执行 `geocode`、`route` 或 `search-poi` 前,先确认高德 API Key 已配置
15
15
  - 在执行 `distance` 前,若起终点包含地名,也需要先确认高德 API Key 已配置
16
+ - 如果同一个地点后续还要重复用于 `distance`、`route` 或 `search-poi` 周边搜索,先调用 `geocode` 或 `search-poi` 获取坐标,后续优先传 `经度,纬度`,不要重复传地名,以减少地理编码 API 调用量
17
+ - 结构化地址、地标性名胜景区或建筑物名称优先使用 `geocode` 获取坐标;较模糊的地点名称优先使用 `search-poi` 获取目标坐标
16
18
  - 如果返回 `MISSING_CONFIG`,先执行配置命令,再继续业务调用
17
19
 
18
20
  ## 初始化
@@ -35,6 +37,32 @@ uvx amap-cli config set --api-key <YOUR_AMAP_KEY>
35
37
  uvx amap-cli config show
36
38
  ```
37
39
 
40
+ ## Geocode
41
+
42
+ 需要将结构化地址或地标名称解析为坐标时,使用 `geocode`:
43
+
44
+ ```bash
45
+ uvx amap-cli geocode --address 北京南站
46
+ ```
47
+
48
+ 如果需要缩小解析范围,可追加城市:
49
+
50
+ ```bash
51
+ uvx amap-cli geocode --address 软件园二期 --city 厦门
52
+ ```
53
+
54
+ 关键参数:
55
+
56
+ - `--address`:必填,结构化地址、地标性名胜景区或建筑物名称
57
+ - `--city`:可选,用于缩小地理编码范围
58
+
59
+ 关键约束:
60
+
61
+ - 地理编码仅支持详细的结构化地址,以及地标性名胜景区、建筑物名称
62
+ - 返回结果中的 `geocode.location` 为 `[经度, 纬度]`
63
+ - 返回结果中的 `geocode.formattedAddress` 可用于判断解析是否符合预期
64
+ - 对于较模糊的名称,优先补充更完整的地址信息或追加 `--city`
65
+
38
66
  ## Route
39
67
 
40
68
  需要路径规划时,使用 `route`:
@@ -71,10 +99,17 @@ uvx amap-cli route \
71
99
  - `--type transit` 必须显式传 `--city`
72
100
  - `--waypoints` 和 `--policy` 只能和 `--type driving` 一起使用
73
101
  - `--strategy` 只能和 `--type transit` 一起使用
102
+ - 如果起点、终点或途经点后续会复用,先用 `geocode` 或 `search-poi` 拿到坐标,再将坐标传给 `--from`、`--to`、`--waypoints`
74
103
  - 地理编码仅支持详细的结构化地址,以及地标性名胜景区、建筑物名称
75
104
  - 当输入为上述名称时,可从 `state.from.formattedAddress`、`state.to.formattedAddress` 以及 `state.waypoints[*].formattedAddress` 判断解析是否符合预期
76
105
  - 对于其他较模糊的名称,先使用 `search-poi` 获取目标坐标,再调用 `route`
77
106
 
107
+ 推荐流程:
108
+
109
+ 1. 首次解析地点时,使用 `geocode` 或 `search-poi` 获取坐标
110
+ 2. 后续多次规划路线时,统一复用坐标
111
+ 3. 如需保留地点语义,可配合 `--from-name` / `--to-name`
112
+
78
113
  ## Distance
79
114
 
80
115
  需要计算两个地点之间的直线距离时,使用 `distance`:
@@ -103,6 +138,7 @@ uvx amap-cli distance \
103
138
 
104
139
  - 当 `--from` 和 `--to` 都是坐标时,直接本地计算,不依赖 API Key
105
140
  - 任一输入为结构化地址或地标性名胜景区、建筑物名称时,会先调用地理编码接口解析坐标
141
+ - 如果某个地点后续还会继续参与其他计算,首次解析后应复用坐标,避免再次以地名触发地理编码
106
142
  - 当输入为上述名称时,可从 `state.from.formattedAddress` / `state.to.formattedAddress` 判断地理编码是否符合预期
107
143
  - 对于其他较模糊的名称,先使用 `search-poi` 获取目标坐标,再调用 `distance`
108
144
  - 结果中的 `summary.distance` 单位为米
@@ -141,6 +177,7 @@ uvx amap-cli search-poi \
141
177
  - 传入 `--center` 时执行周边搜索
142
178
  - 未传入 `--center` 时执行关键词搜索
143
179
  - `--radius` 只有在传入 `--center` 时才应该使用
180
+ - 如果要围绕某个地点反复做周边搜索,先使用 `geocode` 或 `search-poi` 获取该地点坐标,再持续复用 `--center`,不要每次都传地点名称重新解析
144
181
 
145
182
  ## 输出约定
146
183
 
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import json
6
+ import time
6
7
  from typing import Any
7
8
  from urllib.error import HTTPError, URLError
8
9
  from urllib.parse import urlencode
@@ -39,32 +40,36 @@ class AmapApiClient:
39
40
  )
40
41
 
41
42
  try:
42
- with urlopen(request, timeout=self.config.timeout_seconds) as response:
43
- body = response.read().decode("utf-8")
44
- except HTTPError as exc:
45
- response_body = exc.read().decode("utf-8", errors="ignore")
46
- raise ApiRequestError(
47
- f"高德 API HTTP 请求失败:{exc.code}",
48
- details={"status": exc.code, "body": response_body},
49
- ) from exc
50
- except URLError as exc:
51
- raise ApiRequestError(
52
- "无法连接高德 API。",
53
- details={"reason": str(exc.reason)},
54
- ) from exc
55
- except TimeoutError as exc:
56
- raise ApiRequestError("请求高德 API 超时。") from exc
43
+ try:
44
+ with urlopen(request, timeout=self.config.timeout_seconds) as response:
45
+ body = response.read().decode("utf-8")
46
+ except HTTPError as exc:
47
+ response_body = exc.read().decode("utf-8", errors="ignore")
48
+ raise ApiRequestError(
49
+ f"高德 API HTTP 请求失败:{exc.code}",
50
+ details={"status": exc.code, "body": response_body},
51
+ ) from exc
52
+ except URLError as exc:
53
+ raise ApiRequestError(
54
+ "无法连接高德 API。",
55
+ details={"reason": str(exc.reason)},
56
+ ) from exc
57
+ except TimeoutError as exc:
58
+ raise ApiRequestError("请求高德 API 超时。") from exc
57
59
 
58
- try:
59
- payload = json.loads(body)
60
- except json.JSONDecodeError as exc:
61
- raise ApiRequestError(
62
- "高德 API 返回了非 JSON 响应。",
63
- details={"body": body[:500]},
64
- ) from exc
60
+ try:
61
+ payload = json.loads(body)
62
+ except json.JSONDecodeError as exc:
63
+ raise ApiRequestError(
64
+ "高德 API 返回了非 JSON 响应。",
65
+ details={"body": body[:500]},
66
+ ) from exc
65
67
 
66
- self._raise_for_amap_error(payload)
67
- return payload
68
+ self._raise_for_amap_error(payload)
69
+ return payload
70
+ finally:
71
+ if self.config.request_sleep_seconds > 0:
72
+ time.sleep(self.config.request_sleep_seconds)
68
73
 
69
74
  def _build_url(self, path: str, params: dict[str, Any]) -> str:
70
75
  """Build a full request URL from config and query params."""
@@ -8,6 +8,7 @@ from typing import Any, Sequence
8
8
  from amap_cli.config import get_config_path, load_config, save_config
9
9
  from amap_cli.distance import register_distance_command
10
10
  from amap_cli.errors import AmapCliError, ValidationError
11
+ from amap_cli.geocode_command import register_geocode_command
11
12
  from amap_cli.output import emit_error, emit_success
12
13
  from amap_cli.route import register_route_command
13
14
  from amap_cli.search_poi import register_search_poi_command
@@ -31,6 +32,7 @@ def build_parser() -> JsonArgumentParser:
31
32
 
32
33
  _register_config_command(subparsers)
33
34
  register_install_skill_command(subparsers)
35
+ register_geocode_command(subparsers)
34
36
  register_distance_command(subparsers)
35
37
  register_route_command(subparsers)
36
38
  register_search_poi_command(subparsers)
@@ -54,6 +56,11 @@ def _register_config_command(
54
56
  type=float,
55
57
  help="请求超时时间(秒)",
56
58
  )
59
+ set_parser.add_argument(
60
+ "--request-sleep-seconds",
61
+ type=float,
62
+ help="每次高德 API 调用结束后的 sleep 时间(秒,0 表示关闭)",
63
+ )
57
64
  set_parser.set_defaults(handler=_handle_config_set)
58
65
 
59
66
  show_parser = config_subparsers.add_parser("show", help="查看当前配置(敏感信息脱敏)")
@@ -71,15 +78,17 @@ def _handle_config_set(args: argparse.Namespace) -> dict[str, Any]:
71
78
  args.api_key is None
72
79
  and args.base_url is None
73
80
  and args.timeout_seconds is None
81
+ and args.request_sleep_seconds is None
74
82
  ):
75
83
  raise ValidationError(
76
- "请至少提供一个配置项,例如 `--api-key`、`--base-url` 或 `--timeout-seconds`。"
84
+ "请至少提供一个配置项,例如 `--api-key`、`--base-url`、`--timeout-seconds` 或 `--request-sleep-seconds`。"
77
85
  )
78
86
 
79
87
  config = save_config(
80
88
  api_key=args.api_key,
81
89
  base_url=args.base_url,
82
90
  timeout_seconds=args.timeout_seconds,
91
+ request_sleep_seconds=args.request_sleep_seconds,
83
92
  )
84
93
  return {
85
94
  "message": "配置已保存。",
@@ -15,6 +15,7 @@ APP_NAME = "amap-cli"
15
15
  CONFIG_FILE_NAME = "config.json"
16
16
  DEFAULT_BASE_URL = "https://restapi.amap.com"
17
17
  DEFAULT_TIMEOUT_SECONDS = 10.0
18
+ DEFAULT_REQUEST_SLEEP_SECONDS = 0.34
18
19
 
19
20
 
20
21
  @dataclass(slots=True)
@@ -24,6 +25,7 @@ class AmapConfig:
24
25
  api_key: str | None = None
25
26
  base_url: str = DEFAULT_BASE_URL
26
27
  timeout_seconds: float = DEFAULT_TIMEOUT_SECONDS
28
+ request_sleep_seconds: float = DEFAULT_REQUEST_SLEEP_SECONDS
27
29
 
28
30
  @classmethod
29
31
  def from_dict(cls, raw: dict[str, Any]) -> "AmapConfig":
@@ -31,6 +33,10 @@ class AmapConfig:
31
33
  api_key = raw.get("api_key")
32
34
  base_url = raw.get("base_url", DEFAULT_BASE_URL)
33
35
  timeout_seconds = raw.get("timeout_seconds", DEFAULT_TIMEOUT_SECONDS)
36
+ request_sleep_seconds = raw.get(
37
+ "request_sleep_seconds",
38
+ DEFAULT_REQUEST_SLEEP_SECONDS,
39
+ )
34
40
 
35
41
  if api_key is not None and not isinstance(api_key, str):
36
42
  raise ConfigError("配置文件中的 api_key 格式无效。")
@@ -38,6 +44,11 @@ class AmapConfig:
38
44
  raise ConfigError("配置文件中的 base_url 格式无效。")
39
45
  if not isinstance(timeout_seconds, (int, float)) or timeout_seconds <= 0:
40
46
  raise ConfigError("配置文件中的 timeout_seconds 格式无效。")
47
+ if (
48
+ not isinstance(request_sleep_seconds, (int, float))
49
+ or request_sleep_seconds < 0
50
+ ):
51
+ raise ConfigError("配置文件中的 request_sleep_seconds 格式无效。")
41
52
 
42
53
  normalized_api_key = None
43
54
  if api_key is not None:
@@ -47,6 +58,7 @@ class AmapConfig:
47
58
  api_key=normalized_api_key,
48
59
  base_url=base_url.strip(),
49
60
  timeout_seconds=float(timeout_seconds),
61
+ request_sleep_seconds=float(request_sleep_seconds),
50
62
  )
51
63
 
52
64
  def to_dict(self, *, mask_secrets: bool = False) -> dict[str, Any]:
@@ -149,6 +161,7 @@ def save_config(
149
161
  api_key: str | None = None,
150
162
  base_url: str | None = None,
151
163
  timeout_seconds: float | None = None,
164
+ request_sleep_seconds: float | None = None,
152
165
  ) -> AmapConfig:
153
166
  """Persist config updates to the OS-specific config path."""
154
167
  config = load_config(required=False) or AmapConfig()
@@ -170,6 +183,11 @@ def save_config(
170
183
  raise ValidationError("`--timeout-seconds` 必须大于 0。")
171
184
  config.timeout_seconds = float(timeout_seconds)
172
185
 
186
+ if request_sleep_seconds is not None:
187
+ if request_sleep_seconds < 0:
188
+ raise ValidationError("`--request-sleep-seconds` 不能小于 0。")
189
+ config.request_sleep_seconds = float(request_sleep_seconds)
190
+
173
191
  path = get_config_path()
174
192
  try:
175
193
  path.parent.mkdir(parents=True, exist_ok=True)
@@ -0,0 +1,91 @@
1
+ """Geocode command registration and handlers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from dataclasses import dataclass
7
+ from typing import Any
8
+
9
+ from amap_cli.api import AmapApiClient
10
+ from amap_cli.geocode import GeocodeResult, geocode_text
11
+ from amap_cli.params import normalize_text_argument
12
+
13
+
14
+ @dataclass(frozen=True, slots=True)
15
+ class GeocodeRequest:
16
+ """Validated geocode command input."""
17
+
18
+ address: str
19
+ city: str | None
20
+
21
+ @classmethod
22
+ def from_args(cls, args: argparse.Namespace) -> "GeocodeRequest":
23
+ """Build a validated geocode request from parsed CLI arguments."""
24
+ return cls(
25
+ address=normalize_text_argument(args.address, "address"),
26
+ city=(
27
+ normalize_text_argument(args.city, "city")
28
+ if args.city is not None
29
+ else None
30
+ ),
31
+ )
32
+
33
+ def build_state(self) -> dict[str, Any]:
34
+ """Build CLI-facing state metadata."""
35
+ state: dict[str, Any] = {"address": self.address}
36
+ if self.city is not None:
37
+ state["city"] = self.city
38
+ return state
39
+
40
+
41
+ def register_geocode_command(
42
+ subparsers: argparse._SubParsersAction[argparse.ArgumentParser],
43
+ ) -> argparse.ArgumentParser:
44
+ """Register the `geocode` command on a subparser collection."""
45
+ parser = subparsers.add_parser(
46
+ "geocode",
47
+ help="将地点名称解析为坐标",
48
+ description="调用高德地理编码接口,将结构化地址或地标名称解析为坐标。",
49
+ )
50
+ parser.add_argument(
51
+ "--address",
52
+ required=True,
53
+ help="待解析的结构化地址或地标名称",
54
+ )
55
+ parser.add_argument(
56
+ "--city",
57
+ help="可选城市,用于缩小地理编码范围",
58
+ )
59
+ parser.set_defaults(handler=handle_geocode_command)
60
+ return parser
61
+
62
+
63
+ def handle_geocode_command(
64
+ args: argparse.Namespace,
65
+ *,
66
+ client: AmapApiClient | None = None,
67
+ ) -> dict[str, Any]:
68
+ """Handle the `geocode` command and normalize the response."""
69
+ request = GeocodeRequest.from_args(args)
70
+ result = geocode_text(
71
+ request.address,
72
+ client=client or AmapApiClient(),
73
+ city=request.city,
74
+ )
75
+ return {
76
+ "state": request.build_state(),
77
+ "geocode": _normalize_geocode_result(result),
78
+ }
79
+
80
+
81
+ def _normalize_geocode_result(result: GeocodeResult) -> dict[str, Any]:
82
+ """Normalize the geocode result to the CLI output shape."""
83
+ payload: dict[str, Any] = {
84
+ "location": [
85
+ round(result.coordinate.longitude, 6),
86
+ round(result.coordinate.latitude, 6),
87
+ ]
88
+ }
89
+ if result.formatted_address is not None:
90
+ payload["formattedAddress"] = result.formatted_address
91
+ return payload
@@ -4,5 +4,5 @@ requires-python = ">=3.11"
4
4
 
5
5
  [[package]]
6
6
  name = "amap-cli"
7
- version = "0.1.2"
7
+ version = "0.1.4"
8
8
  source = { editable = "." }
File without changes
File without changes
File without changes
File without changes