jiff-geo-cli 0.1.1__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.
geo_cli/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ from geo_cli.cli import app
2
+
3
+ if __name__ == "__main__":
4
+ app()
@@ -0,0 +1 @@
1
+ """Resource-specific Geo API clients."""
geo_cli/api/article.py ADDED
@@ -0,0 +1,229 @@
1
+ """Browser publish service client for article account and job workflows."""
2
+
3
+ from typing import Any
4
+
5
+ import httpx
6
+
7
+ from geo_cli.config import (
8
+ get_article_api_key,
9
+ get_article_base_url,
10
+ get_article_group_id,
11
+ get_article_group_text,
12
+ )
13
+
14
+
15
+ def require_group_id() -> str:
16
+ group_id = get_article_group_id()
17
+ if not group_id:
18
+ raise ValueError("ARTICLE_GROUP_ID 未配置")
19
+ return group_id
20
+
21
+
22
+ def _headers() -> dict[str, str]:
23
+ api_key = get_article_api_key()
24
+ if not api_key:
25
+ raise ValueError("ARTICLE_API_KEY 未配置")
26
+ return {
27
+ "Content-Type": "application/json",
28
+ "Authorization": f"Bearer {api_key}",
29
+ }
30
+
31
+
32
+ def _url(path: str) -> str:
33
+ return f"{get_article_base_url().rstrip('/')}{path}"
34
+
35
+
36
+ def _request(
37
+ method: str,
38
+ path: str,
39
+ *,
40
+ params: dict[str, Any] | None = None,
41
+ json_body: dict[str, Any] | None = None,
42
+ timeout: float = 60.0,
43
+ ) -> dict[str, Any]:
44
+ response = httpx.request(
45
+ method,
46
+ _url(path),
47
+ params={key: value for key, value in (params or {}).items() if value is not None},
48
+ json=json_body,
49
+ headers=_headers(),
50
+ timeout=timeout,
51
+ )
52
+ try:
53
+ data = response.json()
54
+ except Exception:
55
+ data = {"detail": response.text}
56
+ if response.status_code >= 400:
57
+ return {"code": response.status_code, "msg": data}
58
+ return data
59
+
60
+
61
+ def list_platforms() -> dict[str, Any]:
62
+ return _request("GET", "/api/v1/platforms")
63
+
64
+
65
+ def create_login_session(platform: str) -> dict[str, Any]:
66
+ return _request("POST", "/api/v1/login-sessions", json_body={"platform": platform})
67
+
68
+
69
+ def get_login_session(session_id: str) -> dict[str, Any]:
70
+ return _request("GET", f"/api/v1/login-sessions/{session_id}")
71
+
72
+
73
+ def cancel_login_session(session_id: str) -> dict[str, Any]:
74
+ return _request("DELETE", f"/api/v1/login-sessions/{session_id}")
75
+
76
+
77
+ def list_accounts(
78
+ platform: str | None = None,
79
+ status: str | None = None,
80
+ include_channel: bool = False,
81
+ include_runtime: bool = False,
82
+ limit: int = 100,
83
+ offset: int = 0,
84
+ ) -> dict[str, Any]:
85
+ return _request(
86
+ "GET",
87
+ "/api/v1/accounts/all",
88
+ params={
89
+ "group_id": require_group_id(),
90
+ "group_text": get_article_group_text() or None,
91
+ "platform": platform,
92
+ "status": status,
93
+ "include_channel": include_channel,
94
+ "include_runtime": include_runtime,
95
+ "limit": limit,
96
+ "offset": offset,
97
+ },
98
+ )
99
+
100
+
101
+ def list_available_accounts(
102
+ platform: str | None = None,
103
+ include_channel: bool = False,
104
+ include_runtime: bool = False,
105
+ limit: int = 100,
106
+ offset: int = 0,
107
+ ) -> dict[str, Any]:
108
+ return _request(
109
+ "GET",
110
+ "/api/v1/accounts/available",
111
+ params={
112
+ "group_id": require_group_id(),
113
+ "group_text": get_article_group_text() or None,
114
+ "platform": platform,
115
+ "include_channel": include_channel,
116
+ "include_runtime": include_runtime,
117
+ "limit": limit,
118
+ "offset": offset,
119
+ },
120
+ )
121
+
122
+
123
+ def get_account(
124
+ platform: str,
125
+ phone: str,
126
+ include_channel: bool = True,
127
+ include_runtime: bool = False,
128
+ ) -> dict[str, Any]:
129
+ return _request(
130
+ "GET",
131
+ f"/api/v1/accounts/{platform}/{phone}",
132
+ params={
133
+ "group_id": require_group_id(),
134
+ "include_channel": include_channel,
135
+ "include_runtime": include_runtime,
136
+ },
137
+ )
138
+
139
+
140
+ def bind_account(
141
+ platform: str,
142
+ phone: str,
143
+ channel_id: str,
144
+ status: str = "normal",
145
+ reset_failures: bool = True,
146
+ consecutive_failures: int = 0,
147
+ ) -> dict[str, Any]:
148
+ return _request(
149
+ "PUT",
150
+ f"/api/v1/accounts/{platform}/{phone}",
151
+ json_body={
152
+ "group_id": require_group_id(),
153
+ "group_text": get_article_group_text(),
154
+ "channel_id": channel_id,
155
+ "status": status,
156
+ "reset_failures": reset_failures,
157
+ "consecutive_failures": consecutive_failures,
158
+ },
159
+ )
160
+
161
+
162
+ def update_account(
163
+ platform: str,
164
+ phone: str,
165
+ group_text: str | None = None,
166
+ new_phone: str | None = None,
167
+ status: str | None = None,
168
+ reset_failures: bool | None = None,
169
+ consecutive_failures: int | None = None,
170
+ ) -> dict[str, Any]:
171
+ return _request(
172
+ "PATCH",
173
+ f"/api/v1/accounts/{platform}/{phone}",
174
+ json_body={
175
+ "group_id": require_group_id(),
176
+ "group_text": group_text,
177
+ "new_phone": new_phone,
178
+ "status": status,
179
+ "reset_failures": reset_failures,
180
+ "consecutive_failures": consecutive_failures,
181
+ },
182
+ )
183
+
184
+
185
+ def delete_account(
186
+ platform: str,
187
+ phone: str,
188
+ force: bool = False,
189
+ delete_channel: bool = True,
190
+ ) -> dict[str, Any]:
191
+ return _request(
192
+ "DELETE",
193
+ f"/api/v1/accounts/{platform}/{phone}",
194
+ params={
195
+ "group_id": require_group_id(),
196
+ "force": force,
197
+ "delete_channel": delete_channel,
198
+ },
199
+ )
200
+
201
+
202
+ def create_job(
203
+ channel_id: str,
204
+ title: str,
205
+ content: str,
206
+ cover_image_url: str | None = None,
207
+ ) -> dict[str, Any]:
208
+ return _request(
209
+ "POST",
210
+ "/api/v1/jobs",
211
+ json_body={
212
+ "channel_id": channel_id,
213
+ "title": title,
214
+ "content": content,
215
+ "cover_image_url": cover_image_url,
216
+ },
217
+ )
218
+
219
+
220
+ def get_job(job_id: str) -> dict[str, Any]:
221
+ return _request("GET", f"/api/v1/jobs/{job_id}")
222
+
223
+
224
+ def save_job_cookie(job_id: str) -> dict[str, Any]:
225
+ return _request("POST", f"/api/v1/jobs/{job_id}/save-cookie")
226
+
227
+
228
+ def cancel_job(job_id: str) -> dict[str, Any]:
229
+ return _request("POST", f"/api/v1/jobs/{job_id}/cancel")
@@ -0,0 +1,20 @@
1
+ from typing import Any
2
+
3
+ from geo_cli.core.http import normalize_list, post
4
+
5
+
6
+ def get_auto_job_by_brand(brand_id: int) -> dict[str, Any]:
7
+ return post("/api/v1/auto-job/get-by-brand", {"brand_id": brand_id})
8
+
9
+
10
+ def update_auto_job(
11
+ brand_id: int,
12
+ enable: int | None = None,
13
+ platforms: list[str] | None = None,
14
+ ) -> dict[str, Any]:
15
+ body: dict[str, Any] = {"brand_id": brand_id}
16
+ if enable is not None:
17
+ body["enable"] = enable
18
+ if platforms is not None:
19
+ body["platforms"] = normalize_list(platforms)
20
+ return post("/api/v1/auto-job/update", body)
geo_cli/api/brand.py ADDED
@@ -0,0 +1,45 @@
1
+ from typing import Any
2
+
3
+ from geo_cli.core.http import normalize_list, post
4
+
5
+
6
+ def create_brand(
7
+ name: str,
8
+ description: str,
9
+ platforms: list[str],
10
+ aliases: list[str] | None = None,
11
+ service_type: str | None = None,
12
+ ) -> dict[str, Any]:
13
+ body: dict[str, Any] = {
14
+ "name": name,
15
+ "description": description,
16
+ "platforms": normalize_list(platforms),
17
+ }
18
+ if aliases is not None:
19
+ body["aliases"] = aliases
20
+ if service_type is not None:
21
+ body["service_type"] = service_type
22
+ return post("/api/v1/brand/create", body)
23
+
24
+
25
+ def list_brands(page: int = 1, page_size: int = 20) -> dict[str, Any]:
26
+ return post("/api/v1/brand/list-by-current-user", {"page": page, "page_size": page_size})
27
+
28
+
29
+ def update_brand(
30
+ brand_id: int,
31
+ name: str | None = None,
32
+ description: str | None = None,
33
+ aliases: list[str] | None = None,
34
+ service_type: str | None = None,
35
+ ) -> dict[str, Any]:
36
+ body: dict[str, Any] = {"brand_id": brand_id}
37
+ if name is not None:
38
+ body["name"] = name
39
+ if description is not None:
40
+ body["description"] = description
41
+ if aliases is not None:
42
+ body["aliases"] = aliases
43
+ if service_type is not None:
44
+ body["service_type"] = service_type
45
+ return post("/api/v1/brand/update", body)
@@ -0,0 +1,38 @@
1
+ from typing import Any
2
+
3
+ from geo_cli.core.http import normalize_list, post
4
+
5
+
6
+ def create_eval_job(
7
+ brand_id: int,
8
+ eval_job_name: str,
9
+ query_ids: list[int],
10
+ platforms: list[str],
11
+ ) -> dict[str, Any]:
12
+ return post(
13
+ "/api/v1/eval-job/create",
14
+ {
15
+ "brand_id": brand_id,
16
+ "eval_job_name": eval_job_name,
17
+ "query_ids": query_ids,
18
+ "platforms": normalize_list(platforms),
19
+ },
20
+ )
21
+
22
+
23
+ def get_eval_job_status(brand_id: int, eval_job_id: str) -> dict[str, Any]:
24
+ return post(
25
+ "/api/v1/eval-job/status",
26
+ {"brand_id": brand_id, "eval_job_id": eval_job_id},
27
+ )
28
+
29
+
30
+ def list_eval_jobs_by_brand(
31
+ brand_id: int,
32
+ page: int = 1,
33
+ page_size: int = 20,
34
+ ) -> dict[str, Any]:
35
+ return post(
36
+ "/api/v1/eval-job/list-by-brand",
37
+ {"brand_id": brand_id, "page": page, "page_size": page_size},
38
+ )
geo_cli/api/metrics.py ADDED
@@ -0,0 +1,305 @@
1
+ import json
2
+ from typing import Any
3
+
4
+ import httpx
5
+
6
+ from geo_cli.config import (
7
+ get_report_agent_api_key,
8
+ get_report_agent_base_url,
9
+ get_report_agent_id,
10
+ )
11
+ from geo_cli.core.http import clean_params, get, post
12
+
13
+
14
+ def query_evaluated_metrics(brand_id: int, eval_job_id: str) -> dict[str, Any]:
15
+ return post(
16
+ "/api/v1/eval-job/query_evaluated_metrics",
17
+ {"brand_id": brand_id, "eval_job_id": eval_job_id},
18
+ )
19
+
20
+
21
+ def _get_geo_metrics(
22
+ path: str,
23
+ brand_id: str,
24
+ start_date: str | None = None,
25
+ end_date: str | None = None,
26
+ platform: str | None = None,
27
+ country: str | None = None,
28
+ lang: str | None = None,
29
+ regions: list[str] | None = None,
30
+ key_strengths: list[str] | None = None,
31
+ target_people: list[str] | None = None,
32
+ scenarios: list[str] | None = None,
33
+ ) -> dict[str, Any]:
34
+ return get(
35
+ path,
36
+ clean_params(
37
+ {
38
+ "brand_id": brand_id,
39
+ "start_date": start_date,
40
+ "end_date": end_date,
41
+ "platform": platform,
42
+ "country": country,
43
+ "lang": lang,
44
+ "regions": regions,
45
+ "key_strengths": key_strengths,
46
+ "target_people": target_people,
47
+ "scenarios": scenarios,
48
+ }
49
+ ),
50
+ )
51
+
52
+
53
+ def get_my_brand_stats(
54
+ brand_id: str,
55
+ start_date: str | None = None,
56
+ end_date: str | None = None,
57
+ platform: str | None = None,
58
+ country: str | None = None,
59
+ lang: str | None = None,
60
+ regions: list[str] | None = None,
61
+ key_strengths: list[str] | None = None,
62
+ target_people: list[str] | None = None,
63
+ scenarios: list[str] | None = None,
64
+ ) -> dict[str, Any]:
65
+ return _get_geo_metrics(
66
+ "/api/v1/etl-dashboard/geo/my-brand-stats",
67
+ brand_id,
68
+ start_date,
69
+ end_date,
70
+ platform,
71
+ country,
72
+ lang,
73
+ regions,
74
+ key_strengths,
75
+ target_people,
76
+ scenarios,
77
+ )
78
+
79
+
80
+ def get_all_brands_stats(
81
+ brand_id: str,
82
+ start_date: str | None = None,
83
+ end_date: str | None = None,
84
+ platform: str | None = None,
85
+ country: str | None = None,
86
+ lang: str | None = None,
87
+ regions: list[str] | None = None,
88
+ key_strengths: list[str] | None = None,
89
+ target_people: list[str] | None = None,
90
+ scenarios: list[str] | None = None,
91
+ ) -> dict[str, Any]:
92
+ return _get_geo_metrics(
93
+ "/api/v1/etl-dashboard/geo/all-brands-stats",
94
+ brand_id,
95
+ start_date,
96
+ end_date,
97
+ platform,
98
+ country,
99
+ lang,
100
+ regions,
101
+ key_strengths,
102
+ target_people,
103
+ scenarios,
104
+ )
105
+
106
+
107
+ def get_my_prompt_stats(
108
+ brand_id: str,
109
+ start_date: str | None = None,
110
+ end_date: str | None = None,
111
+ platform: str | None = None,
112
+ country: str | None = None,
113
+ lang: str | None = None,
114
+ regions: list[str] | None = None,
115
+ key_strengths: list[str] | None = None,
116
+ target_people: list[str] | None = None,
117
+ scenarios: list[str] | None = None,
118
+ ) -> dict[str, Any]:
119
+ return _get_geo_metrics(
120
+ "/api/v1/etl-dashboard/geo/my-prompt-stats",
121
+ brand_id,
122
+ start_date,
123
+ end_date,
124
+ platform,
125
+ country,
126
+ lang,
127
+ regions,
128
+ key_strengths,
129
+ target_people,
130
+ scenarios,
131
+ )
132
+
133
+
134
+ def get_domain_stats(
135
+ brand_id: str,
136
+ start_date: str | None = None,
137
+ end_date: str | None = None,
138
+ platform: str | None = None,
139
+ country: str | None = None,
140
+ lang: str | None = None,
141
+ regions: list[str] | None = None,
142
+ key_strengths: list[str] | None = None,
143
+ target_people: list[str] | None = None,
144
+ scenarios: list[str] | None = None,
145
+ ) -> dict[str, Any]:
146
+ return _get_geo_metrics(
147
+ "/api/v1/etl-dashboard/geo/domain-stats",
148
+ brand_id,
149
+ start_date,
150
+ end_date,
151
+ platform,
152
+ country,
153
+ lang,
154
+ regions,
155
+ key_strengths,
156
+ target_people,
157
+ scenarios,
158
+ )
159
+
160
+
161
+ def get_domain_brand_stats(
162
+ brand_id: str,
163
+ start_date: str | None = None,
164
+ end_date: str | None = None,
165
+ platform: str | None = None,
166
+ country: str | None = None,
167
+ lang: str | None = None,
168
+ regions: list[str] | None = None,
169
+ key_strengths: list[str] | None = None,
170
+ target_people: list[str] | None = None,
171
+ scenarios: list[str] | None = None,
172
+ ) -> dict[str, Any]:
173
+ return _get_geo_metrics(
174
+ "/api/v1/etl-dashboard/geo/domain-brand-stats",
175
+ brand_id,
176
+ start_date,
177
+ end_date,
178
+ platform,
179
+ country,
180
+ lang,
181
+ regions,
182
+ key_strengths,
183
+ target_people,
184
+ scenarios,
185
+ )
186
+
187
+
188
+ def get_url_stats(
189
+ brand_id: str,
190
+ start_date: str | None = None,
191
+ end_date: str | None = None,
192
+ platform: str | None = None,
193
+ country: str | None = None,
194
+ lang: str | None = None,
195
+ regions: list[str] | None = None,
196
+ key_strengths: list[str] | None = None,
197
+ target_people: list[str] | None = None,
198
+ scenarios: list[str] | None = None,
199
+ ) -> dict[str, Any]:
200
+ return _get_geo_metrics(
201
+ "/api/v1/etl-dashboard/geo/url-stats",
202
+ brand_id,
203
+ start_date,
204
+ end_date,
205
+ platform,
206
+ country,
207
+ lang,
208
+ regions,
209
+ key_strengths,
210
+ target_people,
211
+ scenarios,
212
+ )
213
+
214
+
215
+ def get_qa_details(
216
+ chat_date: str,
217
+ brand_id: str,
218
+ platform: str,
219
+ question_id: str | None = None,
220
+ question: str | None = None,
221
+ ) -> dict[str, Any]:
222
+ return get(
223
+ "/api/v1/etl-dashboard/geo/qa-details",
224
+ clean_params(
225
+ {
226
+ "chat_date": chat_date,
227
+ "brand_id": brand_id,
228
+ "platform": platform,
229
+ "question_id": question_id,
230
+ "question": question,
231
+ }
232
+ ),
233
+ )
234
+
235
+
236
+ def _iter_sse_data(response: httpx.Response):
237
+ for line in response.iter_lines():
238
+ if isinstance(line, bytes):
239
+ line = line.decode("utf-8")
240
+ if not line or not line.startswith("data: "):
241
+ continue
242
+ yield json.loads(line[6:])
243
+
244
+
245
+ def analyze_report(
246
+ brand_id: str,
247
+ message: str,
248
+ session_id: str | None = None,
249
+ ) -> dict[str, Any]:
250
+ api_key = get_report_agent_api_key()
251
+ if not api_key:
252
+ return {"code": -1, "msg": "REPORT_AGENT_API_KEY 未配置"}
253
+
254
+ payload: dict[str, Any] = {
255
+ "message": message,
256
+ "user_id": "geo-cli",
257
+ "stream": False,
258
+ "return_thinking": False,
259
+ "verbose_mode": False,
260
+ "input_params": {"brand_id": brand_id},
261
+ }
262
+ if session_id:
263
+ payload["session_id"] = session_id
264
+
265
+ url = (
266
+ f"{get_report_agent_base_url().rstrip('/')}"
267
+ f"/api/v1/chat/{get_report_agent_id()}"
268
+ )
269
+ response = httpx.post(
270
+ url,
271
+ json=payload,
272
+ headers={
273
+ "Content-Type": "application/json",
274
+ "Authorization": f"Bearer {api_key}",
275
+ },
276
+ timeout=1800.0,
277
+ )
278
+ try:
279
+ response.raise_for_status()
280
+ except httpx.HTTPStatusError as exc:
281
+ return {"code": response.status_code, "msg": response.text or str(exc)}
282
+
283
+ content_parts: list[str] = []
284
+ done: dict[str, Any] = {}
285
+ errors: list[str] = []
286
+ for event in _iter_sse_data(response):
287
+ event_type = event.get("type")
288
+ if event_type == "message":
289
+ content_parts.append(str(event.get("content", "")))
290
+ elif event_type == "done":
291
+ done = event
292
+ elif event_type == "error":
293
+ errors.append(str(event.get("message", "")))
294
+
295
+ if errors:
296
+ return {"code": -1, "msg": "\n".join(error for error in errors if error)}
297
+
298
+ return {
299
+ "code": 0,
300
+ "data": {
301
+ "content": "".join(content_parts),
302
+ "session_id": done.get("session_id"),
303
+ "request_id": done.get("request_id"),
304
+ },
305
+ }