openboost-cli 0.1.0__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.
Files changed (76) hide show
  1. openboost_cli-0.1.0.dist-info/METADATA +153 -0
  2. openboost_cli-0.1.0.dist-info/RECORD +76 -0
  3. openboost_cli-0.1.0.dist-info/WHEEL +5 -0
  4. openboost_cli-0.1.0.dist-info/entry_points.txt +2 -0
  5. openboost_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
  6. openboost_cli-0.1.0.dist-info/top_level.txt +1 -0
  7. proboost_cli/__init__.py +21 -0
  8. proboost_cli/cli/__init__.py +1 -0
  9. proboost_cli/cli/interactive.py +107 -0
  10. proboost_cli/cli/main.py +566 -0
  11. proboost_cli/client.py +58 -0
  12. proboost_cli/coerce_args.py +62 -0
  13. proboost_cli/config.py +109 -0
  14. proboost_cli/mcp_client.py +113 -0
  15. proboost_cli/parse_result.py +53 -0
  16. proboost_cli/registry.py +112 -0
  17. proboost_cli/schemas/__init__.py +1 -0
  18. proboost_cli/schemas/amazon/__init__.py +1 -0
  19. proboost_cli/schemas/amazon/amz_category_query.json +20 -0
  20. proboost_cli/schemas/amazon/amz_hot_amz_hot_cat_tree.json +25 -0
  21. proboost_cli/schemas/amazon/amz_hot_amz_hot_list.json +33 -0
  22. proboost_cli/schemas/amazon/amz_market_price.json +28 -0
  23. proboost_cli/schemas/amazon/amz_product_selection.json +140 -0
  24. proboost_cli/schemas/amazon/amz_review_query.json +28 -0
  25. proboost_cli/schemas/amazon/amz_sales_query.json +20 -0
  26. proboost_cli/schemas/amazon/amz_sku_query.json +20 -0
  27. proboost_cli/schemas/patent/__init__.py +1 -0
  28. proboost_cli/schemas/patent/patent_abstract_image.json +17 -0
  29. proboost_cli/schemas/patent/patent_abstract_translated.json +28 -0
  30. proboost_cli/schemas/patent/patent_bibliography.json +17 -0
  31. proboost_cli/schemas/patent/patent_claim_data.json +21 -0
  32. proboost_cli/schemas/patent/patent_claim_translated.json +28 -0
  33. proboost_cli/schemas/patent/patent_description.json +21 -0
  34. proboost_cli/schemas/patent/patent_description_translated.json +28 -0
  35. proboost_cli/schemas/patent/patent_family.json +17 -0
  36. proboost_cli/schemas/patent/patent_fulltext_image.json +29 -0
  37. proboost_cli/schemas/patent/patent_image_search_multiple.json +117 -0
  38. proboost_cli/schemas/patent/patent_image_search_single.json +114 -0
  39. proboost_cli/schemas/patent/patent_legal_status.json +17 -0
  40. proboost_cli/schemas/patent/patent_pdf.json +21 -0
  41. proboost_cli/schemas/patent/patent_query_count.json +36 -0
  42. proboost_cli/schemas/patent/patent_query_search.json +40 -0
  43. proboost_cli/schemas/patent/patent_query_search_with_agg.json +55 -0
  44. proboost_cli/schemas/patent/patent_semantic_search.json +48 -0
  45. proboost_cli/schemas/tiktok/__init__.py +1 -0
  46. proboost_cli/schemas/tiktok/tk_expert_sale_show.json +20 -0
  47. proboost_cli/schemas/tiktok/tt_commodity_detail.json +21 -0
  48. proboost_cli/schemas/tiktok/tt_commodity_expert_draw_expert_trend.json +20 -0
  49. proboost_cli/schemas/tiktok/tt_commodity_get_commodity_cat_tree.json +8 -0
  50. proboost_cli/schemas/tiktok/tt_commodity_info_list.json +180 -0
  51. proboost_cli/schemas/tiktok/tt_commodity_sales_trend.json +33 -0
  52. proboost_cli/schemas/tiktok/tt_commodity_statistics_get_category_statistics.json +28 -0
  53. proboost_cli/schemas/tiktok/tt_commodity_statistics_get_expert_statistics.json +28 -0
  54. proboost_cli/schemas/tiktok/tt_commodity_statistics_get_price_statistics.json +28 -0
  55. proboost_cli/schemas/tiktok/tt_commodity_statistics_get_total_statistics.json +28 -0
  56. proboost_cli/schemas/tiktok/tt_commodity_voice_analyze.json +36 -0
  57. proboost_cli/schemas/tiktok/tt_expert_basic_analyze.json +24 -0
  58. proboost_cli/schemas/tiktok/tt_expert_category_analyze.json +29 -0
  59. proboost_cli/schemas/tiktok/tt_expert_detail.json +20 -0
  60. proboost_cli/schemas/tiktok/tt_expert_fans_analyze.json +20 -0
  61. proboost_cli/schemas/tiktok/tt_expert_fans_portrait.json +20 -0
  62. proboost_cli/schemas/tiktok/tt_expert_info_list.json +180 -0
  63. proboost_cli/schemas/tiktok/tt_sale_channel_sale_channel.json +26 -0
  64. proboost_cli/schemas/tiktok/tt_shop_commodity_account_list.json +28 -0
  65. proboost_cli/schemas/tiktok/tt_shop_detail.json +28 -0
  66. proboost_cli/schemas/tiktok/tt_shop_info_list.json +108 -0
  67. proboost_cli/schemas/tiktok/tt_shop_market_info.json +28 -0
  68. proboost_cli/schemas/tiktok/tt_shop_sales_trend.json +33 -0
  69. proboost_cli/schemas/tiktok/tt_video_content.json +20 -0
  70. proboost_cli/schemas/tiktok/tt_video_detail.json +20 -0
  71. proboost_cli/schemas/tiktok/tt_video_info_list.json +152 -0
  72. proboost_cli/schemas/tiktok/tt_video_new_video_summary.json +20 -0
  73. proboost_cli/schemas/tiktok/tt_video_script_diy_query.json +20 -0
  74. proboost_cli/schemas/tiktok/tt_video_voice_read.json +28 -0
  75. proboost_cli/schemas/tiktok/tt_video_voice_summary.json +28 -0
  76. proboost_cli/schemas_loader.py +49 -0
@@ -0,0 +1,566 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import json
5
+ import os
6
+ import sys
7
+ from pathlib import Path
8
+ from typing import Annotated, Any, Optional
9
+
10
+ import typer
11
+ from rich.console import Console
12
+ from rich.table import Table
13
+
14
+ from proboost_cli import MCP_LABELS, MCP_NAMES, __version__
15
+ from proboost_cli.cli import interactive
16
+ from proboost_cli.coerce_args import merge_tool_arguments_json
17
+ from proboost_cli.config import (
18
+ effective_secret_and_base,
19
+ load_config,
20
+ mask_secret,
21
+ normalize_mcp_name,
22
+ update_config,
23
+ )
24
+ from proboost_cli.mcp_client import extract_result_text, invoke_tool_async
25
+ from proboost_cli.parse_result import decode_embedded_payload
26
+ from proboost_cli.registry import DOMAIN_LABELS, DOMAIN_TOOLS, TOOLS
27
+ from proboost_cli.schemas_loader import load_tool_definitions, tool_schema_properties, tool_schema_required
28
+
29
+ if sys.stdout and hasattr(sys.stdout, "reconfigure"):
30
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
31
+
32
+ console = Console()
33
+
34
+ app = typer.Typer(
35
+ name="proboost",
36
+ help="Proboost CLI — 统一调用 TikTok / Patent / Amazon MCP",
37
+ no_args_is_help=False,
38
+ add_completion=False,
39
+ )
40
+ config_app = typer.Typer(help="配置 secret-key 和 MCP URL")
41
+ tiktok_app = typer.Typer(help="TikTok MCP")
42
+ tiktok_statistics_app = typer.Typer(help="TikTok 大盘统计")
43
+ tiktok_commodity_app = typer.Typer(help="TikTok 商品选品")
44
+ tiktok_expert_app = typer.Typer(help="TikTok 达人分析")
45
+ tiktok_video_app = typer.Typer(help="TikTok 视频分析")
46
+ tiktok_shop_app = typer.Typer(help="TikTok 店铺分析")
47
+ patent_app = typer.Typer(help="Patent MCP")
48
+ patent_search_app = typer.Typer(help="专利检索")
49
+ patent_detail_app = typer.Typer(help="专利详情")
50
+ patent_image_app = typer.Typer(help="专利图像检索")
51
+ amazon_app = typer.Typer(help="Amazon MCP")
52
+ amazon_product_app = typer.Typer(help="Amazon 商品与市场")
53
+ amazon_hot_app = typer.Typer(help="Amazon 榜单")
54
+
55
+ app.add_typer(config_app, name="config")
56
+ app.add_typer(tiktok_app, name="tiktok")
57
+ app.add_typer(patent_app, name="patent")
58
+ app.add_typer(amazon_app, name="amazon")
59
+ tiktok_app.add_typer(tiktok_statistics_app, name="statistics")
60
+ tiktok_app.add_typer(tiktok_commodity_app, name="commodity")
61
+ tiktok_app.add_typer(tiktok_expert_app, name="expert")
62
+ tiktok_app.add_typer(tiktok_video_app, name="video")
63
+ tiktok_app.add_typer(tiktok_shop_app, name="shop")
64
+ patent_app.add_typer(patent_search_app, name="search")
65
+ patent_app.add_typer(patent_detail_app, name="detail")
66
+ patent_app.add_typer(patent_image_app, name="image-search")
67
+ amazon_app.add_typer(amazon_product_app, name="product")
68
+ amazon_app.add_typer(amazon_hot_app, name="hot")
69
+
70
+ _state: dict[str, str | None] = {"secret_key": None, "base_url": None}
71
+
72
+ KeyOpt = Annotated[Optional[str], typer.Option("--key", "-k", help="secret-key")]
73
+ BaseUrlOpt = Annotated[Optional[str], typer.Option("--base-url", "-u", help="MCP Streamable HTTP URL")]
74
+ McpArg = Annotated[str, typer.Argument(help="MCP 名称: tiktok / patent / amazon")]
75
+ CountryOpt = Annotated[str, typer.Option("--country-region", "--country", help="国家/地区,如 美国")]
76
+ CurrentOpt = Annotated[Optional[int], typer.Option("--current", help="当前页码")]
77
+ SizeOpt = Annotated[Optional[int], typer.Option("--size", help="每页条数")]
78
+ DataPeriodOpt = Annotated[Optional[str], typer.Option("--data-period", help="数据周期,如 last7d / last30d")]
79
+ DateOpt = Annotated[Optional[str], typer.Option("--date", help="数据日期")]
80
+ OrderTypeOpt = Annotated[Optional[str], typer.Option("--order-type", help="排序类型")]
81
+ ExtraArg = Annotated[Optional[list[str]], typer.Argument(help="额外参数 key=value ...")]
82
+
83
+
84
+ @app.callback(invoke_without_command=True)
85
+ def main_callback(ctx: typer.Context, key: KeyOpt = None, base_url: BaseUrlOpt = None) -> None:
86
+ _state["secret_key"] = key
87
+ _state["base_url"] = base_url
88
+ if ctx.invoked_subcommand is None:
89
+ interactive.run_interactive(secret_key=key, base_url=base_url)
90
+
91
+
92
+ def _print_json(data: Any) -> None:
93
+ if isinstance(data, (dict, list)):
94
+ console.print_json(json.dumps(data, ensure_ascii=False))
95
+ else:
96
+ console.print(str(data))
97
+
98
+
99
+ def _props(mcp: str, tool_name: str) -> dict[str, Any]:
100
+ return tool_schema_properties(load_tool_definitions(mcp)[tool_name])
101
+
102
+
103
+ def _parse_extra(mcp: str, tool_name: str, extra: list[str] | None) -> dict[str, Any]:
104
+ return merge_tool_arguments_json({}, tuple(extra or []), props=_props(mcp, tool_name))
105
+
106
+
107
+ def _compact(kwargs: dict[str, Any]) -> dict[str, Any]:
108
+ return {k: v for k, v in kwargs.items() if v is not None}
109
+
110
+
111
+ def _with_extra(mcp: str, tool_name: str, base: dict[str, Any], extra: list[str] | None) -> dict[str, Any]:
112
+ merged = _parse_extra(mcp, tool_name, extra)
113
+ merged.update(_compact(base))
114
+ return merged
115
+
116
+
117
+ def _page_args(current: int | None, size: int | None) -> dict[str, Any]:
118
+ return {"current": current, "size": size}
119
+
120
+
121
+ def _run_tool(mcp: str, tool_name: str, arguments: dict[str, Any], *, verbatim: bool = False) -> None:
122
+ mcp = normalize_mcp_name(mcp)
123
+ secret, base = effective_secret_and_base(
124
+ mcp,
125
+ secret_key=_state.get("secret_key"),
126
+ base_url=_state.get("base_url"),
127
+ )
128
+ if not secret:
129
+ console.print(
130
+ f"[red]缺少 {MCP_LABELS[mcp]} secret-key。[/red]请设置 PROBOOST_SECRET_KEY 或运行 proboost config set --secret-key ..."
131
+ )
132
+ raise typer.Exit(1)
133
+ try:
134
+ result = asyncio.run(invoke_tool_async(base, secret, tool_name, _compact(arguments)))
135
+ except Exception as exc:
136
+ console.print(str(exc), style="red")
137
+ raise typer.Exit(2) from exc
138
+
139
+ text = extract_result_text(result)
140
+ if verbatim:
141
+ console.print(text)
142
+ raise typer.Exit(3 if result.isError else 0)
143
+ if result.isError:
144
+ console.print(text or "(tool error)", style="red")
145
+ raise typer.Exit(3)
146
+ structured = getattr(result, "structuredContent", None)
147
+ if isinstance(structured, (dict, list)):
148
+ _print_json(structured)
149
+ return
150
+ try:
151
+ _print_json(decode_embedded_payload(text))
152
+ except ValueError:
153
+ console.print(text)
154
+
155
+
156
+ @app.command("version")
157
+ def version_cmd() -> None:
158
+ """显示版本号"""
159
+ console.print(__version__)
160
+
161
+
162
+ @app.command("tui")
163
+ def tui_cmd() -> None:
164
+ """进入交互式菜单"""
165
+ interactive.run_interactive(secret_key=_state.get("secret_key"), base_url=_state.get("base_url"))
166
+
167
+
168
+ @config_app.command("set")
169
+ def config_set_cmd(
170
+ secret_key: Annotated[Optional[str], typer.Option("--secret-key", "-k", help="secret-key")] = None,
171
+ mcp: Annotated[Optional[str], typer.Option("--mcp", help="仅配置某个 MCP: tiktok / patent / amazon")] = None,
172
+ base_url: BaseUrlOpt = None,
173
+ ) -> None:
174
+ """保存通用或单 MCP 配置"""
175
+ if secret_key is None and base_url is None:
176
+ console.print("[red]请至少提供 --secret-key 或 --base-url[/red]")
177
+ raise typer.Exit(2)
178
+ update_config(mcp=mcp, secret_key=secret_key, base_url=base_url)
179
+ console.print("[green]配置已保存。[/green]")
180
+
181
+
182
+ @config_app.command("show")
183
+ def config_show_cmd() -> None:
184
+ """查看当前生效配置(密钥脱敏)"""
185
+ cfg = load_config()
186
+ console.print(f"config file: {Path.home() / '.config/proboost/config.json'}")
187
+ if cfg.get("secret_key"):
188
+ console.print(f"global secret_key: {mask_secret(str(cfg['secret_key']))}")
189
+ for mcp in MCP_NAMES:
190
+ secret, base = effective_secret_and_base(mcp)
191
+ console.print(f"{mcp:7} base_url : {base}")
192
+ console.print(f"{mcp:7} secret_key : {mask_secret(secret)}")
193
+ if os.environ.get("PROBOOST_SECRET_KEY"):
194
+ console.print("env PROBOOST_SECRET_KEY is set")
195
+
196
+
197
+ @app.command("list")
198
+ def list_tools_cmd() -> None:
199
+ """列出全部 MCP 工具"""
200
+ for mcp in MCP_NAMES:
201
+ console.print(f"[bold]{MCP_LABELS[mcp]} MCP[/bold]")
202
+ for domain, tool_names in DOMAIN_TOOLS[mcp].items():
203
+ table = Table(title=DOMAIN_LABELS[mcp].get(domain, domain), show_header=True, header_style="bold cyan")
204
+ table.add_column("工具", style="green")
205
+ table.add_column("说明")
206
+ table.add_column("必填参数")
207
+ for tool_name in tool_names:
208
+ meta = TOOLS[mcp][tool_name]
209
+ table.add_row(tool_name, meta.label, ", ".join(meta.required) or "-")
210
+ console.print(table)
211
+ console.print()
212
+ total = sum(len(TOOLS[mcp]) for mcp in MCP_NAMES)
213
+ console.print(f"[bold]共 {total} 个工具[/bold]")
214
+
215
+
216
+ @app.command("docs")
217
+ def docs_cmd(mcp: McpArg, tool_name: str = typer.Argument(..., help="工具名")) -> None:
218
+ """查看工具描述和参数"""
219
+ mcp = normalize_mcp_name(mcp)
220
+ defs = load_tool_definitions(mcp)
221
+ if tool_name not in defs:
222
+ console.print(f"[red]未知工具: {mcp}.{tool_name}[/red]")
223
+ raise typer.Exit(2)
224
+ tool = defs[tool_name]
225
+ console.print(f"[bold]{mcp}.{tool_name}[/bold]")
226
+ console.print(str(tool.get("description", "")))
227
+ props = tool_schema_properties(tool)
228
+ required = set(tool_schema_required(tool))
229
+ table = Table(title="参数", show_header=True, header_style="bold cyan")
230
+ table.add_column("参数")
231
+ table.add_column("类型")
232
+ table.add_column("必填")
233
+ table.add_column("说明")
234
+ for name, schema in props.items():
235
+ if isinstance(schema, dict):
236
+ table.add_row(name, str(schema.get("type", "")), "是" if name in required else "否", str(schema.get("description", ""))[:100])
237
+ console.print(table)
238
+
239
+
240
+ @app.command("call")
241
+ def call_tool_cmd(
242
+ mcp: McpArg,
243
+ tool_name: str = typer.Argument(..., help="MCP 工具名"),
244
+ verbatim: Annotated[bool, typer.Option("--verbatim", "-v", help="输出原始 MCP 文本")] = False,
245
+ payload_file: Annotated[Optional[Path], typer.Option("--payload", "-p", exists=True, dir_okay=False, readable=True, help="JSON 参数文件")] = None,
246
+ payload_stdin: Annotated[bool, typer.Option("--stdin", "-i", help="从 stdin 读取 JSON 参数")] = False,
247
+ set_kv: Annotated[list[str], typer.Option("--set", "-s", help="参数 key=value,可重复")] = [],
248
+ ) -> None:
249
+ """通用 MCP 工具调用"""
250
+ mcp = normalize_mcp_name(mcp)
251
+ defs = load_tool_definitions(mcp)
252
+ if tool_name not in defs:
253
+ console.print(f"[red]未知工具: {mcp}.{tool_name}[/red]")
254
+ raise typer.Exit(2)
255
+ if payload_stdin and payload_file is not None:
256
+ console.print("[red]--stdin 和 --payload 只能使用一个。[/red]")
257
+ raise typer.Exit(2)
258
+
259
+ base_obj: dict[str, Any] = {}
260
+ raw_text: str | None = None
261
+ if payload_stdin:
262
+ raw_text = sys.stdin.read()
263
+ elif payload_file is not None:
264
+ raw_text = payload_file.read_text(encoding="utf-8")
265
+ if raw_text and raw_text.strip():
266
+ decoded = json.loads(raw_text)
267
+ if not isinstance(decoded, dict):
268
+ console.print("[red]payload JSON 必须是对象。[/red]")
269
+ raise typer.Exit(2)
270
+ base_obj = decoded
271
+
272
+ args = merge_tool_arguments_json(base_obj, tuple(set_kv), props=tool_schema_properties(defs[tool_name]))
273
+ _run_tool(mcp, tool_name, args, verbatim=verbatim)
274
+
275
+
276
+ # TikTok shortcuts
277
+ @tiktok_statistics_app.command("total")
278
+ def tiktok_statistics_total(country_region: CountryOpt = "美国", data_period: DataPeriodOpt = "last7d", date: DateOpt = None, extra: ExtraArg = None) -> None:
279
+ _run_tool("tiktok", "tt_commodity_statistics_get_total_statistics", _with_extra("tiktok", "tt_commodity_statistics_get_total_statistics", {"countryRegion": country_region, "dataPeriod": data_period, "date": date}, extra))
280
+
281
+
282
+ @tiktok_statistics_app.command("category")
283
+ def tiktok_statistics_category(country_region: CountryOpt = "美国", data_period: DataPeriodOpt = "last7d", order_type: OrderTypeOpt = None, extra: ExtraArg = None) -> None:
284
+ _run_tool("tiktok", "tt_commodity_statistics_get_category_statistics", _with_extra("tiktok", "tt_commodity_statistics_get_category_statistics", {"countryRegion": country_region, "dataPeriod": data_period, "orderType": order_type}, extra))
285
+
286
+
287
+ @tiktok_statistics_app.command("expert")
288
+ def tiktok_statistics_expert(country_region: CountryOpt = "美国", data_period: DataPeriodOpt = "last7d", extra: ExtraArg = None) -> None:
289
+ _run_tool("tiktok", "tt_commodity_statistics_get_expert_statistics", _with_extra("tiktok", "tt_commodity_statistics_get_expert_statistics", {"countryRegion": country_region, "dataPeriod": data_period}, extra))
290
+
291
+
292
+ @tiktok_statistics_app.command("price")
293
+ def tiktok_statistics_price(country_region: CountryOpt = "美国", data_period: DataPeriodOpt = "last7d", extra: ExtraArg = None) -> None:
294
+ _run_tool("tiktok", "tt_commodity_statistics_get_price_statistics", _with_extra("tiktok", "tt_commodity_statistics_get_price_statistics", {"countryRegion": country_region, "dataPeriod": data_period}, extra))
295
+
296
+
297
+ @tiktok_commodity_app.command("category-tree")
298
+ def tiktok_commodity_category_tree() -> None:
299
+ _run_tool("tiktok", "tt_commodity_get_commodity_cat_tree", {})
300
+
301
+
302
+ @tiktok_commodity_app.command("list")
303
+ def tiktok_commodity_list(country_region: CountryOpt = "美国", current: CurrentOpt = 1, size: SizeOpt = 10, data_period: DataPeriodOpt = None, date: DateOpt = None, commodity_name: Annotated[Optional[str], typer.Option("--commodity-name")] = None, order_type: OrderTypeOpt = None, extra: ExtraArg = None) -> None:
304
+ args = {"countryRegion": country_region, "dataPeriod": data_period, "date": date, "commodityName": commodity_name, "orderType": order_type, **_page_args(current, size)}
305
+ _run_tool("tiktok", "tt_commodity_info_list", _with_extra("tiktok", "tt_commodity_info_list", args, extra))
306
+
307
+
308
+ @tiktok_commodity_app.command("detail")
309
+ def tiktok_commodity_detail(commodity_id: Annotated[str, typer.Argument(help="商品 ID")], country_region: CountryOpt = "美国") -> None:
310
+ _run_tool("tiktok", "tt_commodity_detail", {"countryRegion": country_region, "commodityId": commodity_id})
311
+
312
+
313
+ @tiktok_commodity_app.command("trend")
314
+ def tiktok_commodity_trend(commodity_id: Annotated[str, typer.Argument(help="商品 ID")], country_region: CountryOpt = "美国", past_days: Annotated[int, typer.Option("--past-days")] = 30) -> None:
315
+ _run_tool("tiktok", "tt_commodity_sales_trend", {"countryRegion": country_region, "commodityId": commodity_id, "pastDays": past_days})
316
+
317
+
318
+ @tiktok_commodity_app.command("voice")
319
+ def tiktok_commodity_voice(commodity_id: Annotated[str, typer.Argument(help="商品 ID")], country_region: CountryOpt = "美国", size: SizeOpt = 10) -> None:
320
+ _run_tool("tiktok", "tt_commodity_voice_analyze", {"countryRegion": country_region, "commodityId": commodity_id, "size": size})
321
+
322
+
323
+ @tiktok_commodity_app.command("channel")
324
+ def tiktok_commodity_channel(product_id: Annotated[str, typer.Argument(help="商品 ID")], country_region: CountryOpt = "美国", sale_type: Annotated[int, typer.Option("--sale-type")] = 1) -> None:
325
+ _run_tool("tiktok", "tt_sale_channel_sale_channel", {"countryRegion": country_region, "productId": product_id, "saleType": sale_type})
326
+
327
+
328
+ @tiktok_commodity_app.command("expert-trend")
329
+ def tiktok_commodity_expert_trend(commodity_id: Annotated[str, typer.Argument(help="商品 ID")], country_region: CountryOpt = "美国") -> None:
330
+ _run_tool("tiktok", "tt_commodity_expert_draw_expert_trend", {"countryRegion": country_region, "commodityId": commodity_id})
331
+
332
+
333
+ @tiktok_expert_app.command("list")
334
+ def tiktok_expert_list(country_region: CountryOpt = "美国", current: CurrentOpt = 1, size: SizeOpt = 10, data_period: DataPeriodOpt = None, date: DateOpt = None, expert_name: Annotated[Optional[str], typer.Option("--expert-name")] = None, order_type: OrderTypeOpt = None, extra: ExtraArg = None) -> None:
335
+ args = {"countryRegion": country_region, "dataPeriod": data_period, "date": date, "expertName": expert_name, "orderType": order_type, **_page_args(current, size)}
336
+ _run_tool("tiktok", "tt_expert_info_list", _with_extra("tiktok", "tt_expert_info_list", args, extra))
337
+
338
+
339
+ @tiktok_expert_app.command("detail")
340
+ def tiktok_expert_detail(expert_id: Annotated[str, typer.Argument(help="达人 ID")], country_region: CountryOpt = "美国") -> None:
341
+ _run_tool("tiktok", "tt_expert_detail", {"countryRegion": country_region, "expertId": expert_id})
342
+
343
+
344
+ @tiktok_expert_app.command("fans-portrait")
345
+ def tiktok_expert_fans_portrait(expert_id: Annotated[str, typer.Argument(help="达人 ID")], country_region: CountryOpt = "美国") -> None:
346
+ _run_tool("tiktok", "tt_expert_fans_portrait", {"countryRegion": country_region, "expertId": expert_id})
347
+
348
+
349
+ @tiktok_expert_app.command("sale-show")
350
+ def tiktok_expert_sale_show(expert_id: Annotated[str, typer.Argument(help="达人 ID")], country_region: CountryOpt = "美国") -> None:
351
+ _run_tool("tiktok", "tk_expert_sale_show", {"countryRegion": country_region, "expertId": expert_id})
352
+
353
+
354
+ @tiktok_expert_app.command("basic")
355
+ def tiktok_expert_basic(expert_id: Annotated[str, typer.Argument(help="达人 ID")], country_region: CountryOpt = "美国", extra: ExtraArg = None) -> None:
356
+ _run_tool("tiktok", "tt_expert_basic_analyze", _with_extra("tiktok", "tt_expert_basic_analyze", {"countryRegion": country_region, "expertId": expert_id}, extra))
357
+
358
+
359
+ @tiktok_expert_app.command("category")
360
+ def tiktok_expert_category(commodity_id: Annotated[str, typer.Argument(help="商品 ID")], country_region: CountryOpt = "美国", category_level: Annotated[int, typer.Option("--category-level")] = 1) -> None:
361
+ _run_tool("tiktok", "tt_expert_category_analyze", {"countryRegion": country_region, "commodityId": commodity_id, "categoryLevel": category_level})
362
+
363
+
364
+ @tiktok_expert_app.command("fans")
365
+ def tiktok_expert_fans(commodity_id: Annotated[str, typer.Argument(help="商品 ID")], country_region: CountryOpt = "美国") -> None:
366
+ _run_tool("tiktok", "tt_expert_fans_analyze", {"countryRegion": country_region, "commodityId": commodity_id})
367
+
368
+
369
+ @tiktok_video_app.command("list")
370
+ def tiktok_video_list(country_region: CountryOpt = "美国", current: CurrentOpt = 1, size: SizeOpt = 10, data_period: DataPeriodOpt = None, date: DateOpt = None, order_type: OrderTypeOpt = None, extra: ExtraArg = None) -> None:
371
+ args = {"countryRegion": country_region, "dataPeriod": data_period, "date": date, "orderType": order_type, **_page_args(current, size)}
372
+ _run_tool("tiktok", "tt_video_info_list", _with_extra("tiktok", "tt_video_info_list", args, extra))
373
+
374
+
375
+ @tiktok_video_app.command("detail")
376
+ def tiktok_video_detail(video_id: Annotated[str, typer.Argument(help="视频 ID")], country_region: CountryOpt = "美国") -> None:
377
+ _run_tool("tiktok", "tt_video_detail", {"countryRegion": country_region, "videoId": video_id})
378
+
379
+
380
+ @tiktok_video_app.command("content")
381
+ def tiktok_video_content(video_id: Annotated[str, typer.Argument(help="视频 ID")], country_region: CountryOpt = "美国") -> None:
382
+ _run_tool("tiktok", "tt_video_content", {"countryRegion": country_region, "videoId": video_id})
383
+
384
+
385
+ @tiktok_video_app.command("comments")
386
+ def tiktok_video_comments(video_id: Annotated[str, typer.Argument(help="视频 ID")], country_region: CountryOpt = "美国", current: CurrentOpt = 1, size: SizeOpt = 10) -> None:
387
+ _run_tool("tiktok", "tt_video_voice_read", {"countryRegion": country_region, "videoId": video_id, **_page_args(current, size)})
388
+
389
+
390
+ @tiktok_video_app.command("summary")
391
+ def tiktok_video_summary(video_id: Annotated[str, typer.Argument(help="视频 ID")], country_region: CountryOpt = "美国", size: SizeOpt = 10) -> None:
392
+ _run_tool("tiktok", "tt_video_voice_summary", {"countryRegion": country_region, "videoId": video_id, "size": size})
393
+
394
+
395
+ @tiktok_video_app.command("new-summary")
396
+ def tiktok_video_new_summary(video_id: Annotated[str, typer.Argument(help="视频 ID")], country_region: CountryOpt = "美国") -> None:
397
+ _run_tool("tiktok", "tt_video_new_video_summary", {"countryRegion": country_region, "videoId": video_id})
398
+
399
+
400
+ @tiktok_video_app.command("script")
401
+ def tiktok_video_script(video_id: Annotated[str, typer.Argument(help="视频 ID")], country_region: CountryOpt = "美国") -> None:
402
+ _run_tool("tiktok", "tt_video_script_diy_query", {"countryRegion": country_region, "videoId": video_id})
403
+
404
+
405
+ @tiktok_shop_app.command("list")
406
+ def tiktok_shop_list(data_period: Annotated[str, typer.Option("--data-period", help="数据周期")], country_region: CountryOpt = "美国", current: CurrentOpt = 1, size: SizeOpt = 10, date: DateOpt = None, shop_name: Annotated[Optional[str], typer.Option("--shop-name")] = None, order_type: OrderTypeOpt = None, extra: ExtraArg = None) -> None:
407
+ args = {"countryRegion": country_region, "dataPeriod": data_period, "date": date, "shopName": shop_name, "orderType": order_type, **_page_args(current, size)}
408
+ _run_tool("tiktok", "tt_shop_info_list", _with_extra("tiktok", "tt_shop_info_list", args, extra))
409
+
410
+
411
+ @tiktok_shop_app.command("detail")
412
+ def tiktok_shop_detail(shop_id: Annotated[str, typer.Argument(help="店铺 ID")], country_region: CountryOpt = "美国", data_period: DataPeriodOpt = "last30d", date: DateOpt = None) -> None:
413
+ _run_tool("tiktok", "tt_shop_detail", {"countryRegion": country_region, "dataPeriod": data_period, "date": date, "shopId": shop_id})
414
+
415
+
416
+ @tiktok_shop_app.command("market")
417
+ def tiktok_shop_market(shop_id: Annotated[str, typer.Argument(help="店铺 ID")], country_region: CountryOpt = "美国", data_period: DataPeriodOpt = "last30d", date: DateOpt = None) -> None:
418
+ _run_tool("tiktok", "tt_shop_market_info", {"countryRegion": country_region, "dataPeriod": data_period, "date": date, "shopId": shop_id})
419
+
420
+
421
+ @tiktok_shop_app.command("trend")
422
+ def tiktok_shop_trend(shop_id: Annotated[str, typer.Argument(help="店铺 ID")], country_region: CountryOpt = "美国", past_days: Annotated[int, typer.Option("--past-days")] = 30) -> None:
423
+ _run_tool("tiktok", "tt_shop_sales_trend", {"countryRegion": country_region, "shopId": shop_id, "pastDays": past_days})
424
+
425
+
426
+ @tiktok_shop_app.command("commodity-account")
427
+ def tiktok_shop_commodity_account(shop_id: Annotated[str, typer.Argument(help="店铺 ID")], country_region: CountryOpt = "美国") -> None:
428
+ _run_tool("tiktok", "tt_shop_commodity_account_list", {"countryRegion": country_region, "shopId": shop_id})
429
+
430
+
431
+ # Patent shortcuts
432
+ @patent_search_app.command("query")
433
+ def patent_search_query(query_text: Annotated[str, typer.Argument(help="Analytics 检索式")], limit: Annotated[int, typer.Option("--limit")] = 10, offset: Annotated[int, typer.Option("--offset")] = 0, extra: ExtraArg = None) -> None:
434
+ _run_tool("patent", "patent_query_search", _with_extra("patent", "patent_query_search", {"query_text": query_text, "limit": limit, "offset": offset}, extra))
435
+
436
+
437
+ @patent_search_app.command("semantic")
438
+ def patent_search_semantic(text: Annotated[str, typer.Argument(help="技术描述文本")], limit: Annotated[int, typer.Option("--limit")] = 10, offset: Annotated[int, typer.Option("--offset")] = 0, extra: ExtraArg = None) -> None:
439
+ _run_tool("patent", "patent_semantic_search", _with_extra("patent", "patent_semantic_search", {"text": text, "limit": limit, "offset": offset}, extra))
440
+
441
+
442
+ @patent_search_app.command("count")
443
+ def patent_search_count(query_text: Annotated[str, typer.Argument(help="Analytics 检索式")], extra: ExtraArg = None) -> None:
444
+ _run_tool("patent", "patent_query_count", _with_extra("patent", "patent_query_count", {"query_text": query_text}, extra))
445
+
446
+
447
+ @patent_search_app.command("agg")
448
+ def patent_search_agg(query: Annotated[str, typer.Argument(help="Analytics 检索式")], field: Annotated[str, typer.Argument(help="聚合维度,如 ASSIGNEE,IPC")], limit: Annotated[int, typer.Option("--limit")] = 50, offset: Annotated[int, typer.Option("--offset")] = 0, extra: ExtraArg = None) -> None:
449
+ _run_tool("patent", "patent_query_search_with_agg", _with_extra("patent", "patent_query_search_with_agg", {"query": query, "field": field, "limit": limit, "offset": offset}, extra))
450
+
451
+
452
+ def _patent_id_args(patent_id: str | None, patent_number: str | None, extra: list[str] | None, tool_name: str) -> dict[str, Any]:
453
+ return _with_extra("patent", tool_name, {"patent_id": patent_id, "patent_number": patent_number}, extra)
454
+
455
+
456
+ @patent_detail_app.command("bibliography")
457
+ def patent_detail_bibliography(patent_id: Annotated[Optional[str], typer.Option("--patent-id")] = None, patent_number: Annotated[Optional[str], typer.Option("--patent-number")] = None, extra: ExtraArg = None) -> None:
458
+ _run_tool("patent", "patent_bibliography", _patent_id_args(patent_id, patent_number, extra, "patent_bibliography"))
459
+
460
+
461
+ @patent_detail_app.command("abstract")
462
+ def patent_detail_abstract(patent_id: Annotated[Optional[str], typer.Option("--patent-id")] = None, patent_number: Annotated[Optional[str], typer.Option("--patent-number")] = None, lang: Annotated[Optional[str], typer.Option("--lang")] = None, translated: Annotated[bool, typer.Option("--translated")] = False, extra: ExtraArg = None) -> None:
463
+ tool = "patent_abstract_translated" if translated or lang else "patent_abstract_image"
464
+ args = _patent_id_args(patent_id, patent_number, extra, tool)
465
+ if lang:
466
+ args["lang"] = lang
467
+ _run_tool("patent", tool, args)
468
+
469
+
470
+ @patent_detail_app.command("claim")
471
+ def patent_detail_claim(patent_id: Annotated[Optional[str], typer.Option("--patent-id")] = None, patent_number: Annotated[Optional[str], typer.Option("--patent-number")] = None, lang: Annotated[Optional[str], typer.Option("--lang")] = None, translated: Annotated[bool, typer.Option("--translated")] = False, extra: ExtraArg = None) -> None:
472
+ tool = "patent_claim_translated" if translated or lang else "patent_claim_data"
473
+ args = _patent_id_args(patent_id, patent_number, extra, tool)
474
+ if lang:
475
+ args["lang"] = lang
476
+ _run_tool("patent", tool, args)
477
+
478
+
479
+ @patent_detail_app.command("description")
480
+ def patent_detail_description(patent_id: Annotated[Optional[str], typer.Option("--patent-id")] = None, patent_number: Annotated[Optional[str], typer.Option("--patent-number")] = None, lang: Annotated[Optional[str], typer.Option("--lang")] = None, translated: Annotated[bool, typer.Option("--translated")] = False, extra: ExtraArg = None) -> None:
481
+ tool = "patent_description_translated" if translated or lang else "patent_description"
482
+ args = _patent_id_args(patent_id, patent_number, extra, tool)
483
+ if lang:
484
+ args["lang"] = lang
485
+ _run_tool("patent", tool, args)
486
+
487
+
488
+ @patent_detail_app.command("legal-status")
489
+ def patent_detail_legal_status(patent_id: Annotated[Optional[str], typer.Option("--patent-id")] = None, patent_number: Annotated[Optional[str], typer.Option("--patent-number")] = None, extra: ExtraArg = None) -> None:
490
+ _run_tool("patent", "patent_legal_status", _patent_id_args(patent_id, patent_number, extra, "patent_legal_status"))
491
+
492
+
493
+ @patent_detail_app.command("family")
494
+ def patent_detail_family(patent_id: Annotated[Optional[str], typer.Option("--patent-id")] = None, patent_number: Annotated[Optional[str], typer.Option("--patent-number")] = None, extra: ExtraArg = None) -> None:
495
+ _run_tool("patent", "patent_family", _patent_id_args(patent_id, patent_number, extra, "patent_family"))
496
+
497
+
498
+ @patent_detail_app.command("pdf")
499
+ def patent_detail_pdf(patent_id: Annotated[Optional[str], typer.Option("--patent-id")] = None, patent_number: Annotated[Optional[str], typer.Option("--patent-number")] = None, extra: ExtraArg = None) -> None:
500
+ _run_tool("patent", "patent_pdf", _patent_id_args(patent_id, patent_number, extra, "patent_pdf"))
501
+
502
+
503
+ @patent_detail_app.command("images")
504
+ def patent_detail_images(patent_id: Annotated[Optional[str], typer.Option("--patent-id")] = None, patent_number: Annotated[Optional[str], typer.Option("--patent-number")] = None, extra: ExtraArg = None) -> None:
505
+ _run_tool("patent", "patent_fulltext_image", _patent_id_args(patent_id, patent_number, extra, "patent_fulltext_image"))
506
+
507
+
508
+ @patent_image_app.command("single")
509
+ def patent_image_single(url: Annotated[str, typer.Argument(help="图片公网 URL")], model: Annotated[int, typer.Option("--model")], patent_type: Annotated[str, typer.Option("--patent-type")], limit: Annotated[int, typer.Option("--limit")] = 10, offset: Annotated[int, typer.Option("--offset")] = 0, extra: ExtraArg = None) -> None:
510
+ _run_tool("patent", "patent_image_search_single", _with_extra("patent", "patent_image_search_single", {"url": url, "model": model, "patent_type": patent_type, "limit": limit, "offset": offset}, extra))
511
+
512
+
513
+ @patent_image_app.command("multiple")
514
+ def patent_image_multiple(urls: Annotated[str, typer.Argument(help="逗号分隔 URL 或 JSON 数组")], model: Annotated[int, typer.Option("--model")], patent_type: Annotated[str, typer.Option("--patent-type")], limit: Annotated[int, typer.Option("--limit")] = 10, offset: Annotated[int, typer.Option("--offset")] = 0, extra: ExtraArg = None) -> None:
515
+ parsed_urls: Any = json.loads(urls) if urls.strip().startswith("[") else [u.strip() for u in urls.split(",") if u.strip()]
516
+ _run_tool("patent", "patent_image_search_multiple", _with_extra("patent", "patent_image_search_multiple", {"urls": parsed_urls, "model": model, "patent_type": patent_type, "limit": limit, "offset": offset}, extra))
517
+
518
+
519
+ # Amazon shortcuts
520
+ @amazon_product_app.command("selection")
521
+ def amazon_product_selection(web_site_id: Annotated[str, typer.Option("--web-site-id", "--site")], pages: Annotated[int, typer.Option("--pages")] = 1, pages_size: Annotated[int, typer.Option("--pages-size")] = 10, cat_id: Annotated[Optional[str], typer.Option("--cat-id")] = None, sku_id: Annotated[Optional[str], typer.Option("--sku-id")] = None, extra: ExtraArg = None) -> None:
522
+ args = {"webSiteId": web_site_id, "pages": pages, "pagesSize": pages_size, "catId": cat_id, "skuId": sku_id}
523
+ _run_tool("amazon", "amz_product_selection", _with_extra("amazon", "amz_product_selection", args, extra))
524
+
525
+
526
+ @amazon_product_app.command("sku")
527
+ def amazon_product_sku(sku_id: Annotated[str, typer.Argument(help="SKU ID")], web_site_id: Annotated[Optional[str], typer.Option("--web-site-id", "--site")] = None) -> None:
528
+ _run_tool("amazon", "amz_sku_query", {"skuId": sku_id, "webSiteId": web_site_id})
529
+
530
+
531
+ @amazon_product_app.command("category")
532
+ def amazon_product_category(cat_id: Annotated[str, typer.Argument(help="类目 ID")], web_site_id: Annotated[Optional[str], typer.Option("--web-site-id", "--site")] = None) -> None:
533
+ _run_tool("amazon", "amz_category_query", {"catId": cat_id, "webSiteId": web_site_id})
534
+
535
+
536
+ @amazon_product_app.command("sales")
537
+ def amazon_product_sales(sku_id: Annotated[str, typer.Argument(help="SKU ID")], web_site_id: Annotated[Optional[str], typer.Option("--web-site-id", "--site")] = None) -> None:
538
+ _run_tool("amazon", "amz_sales_query", {"skuId": sku_id, "webSiteId": web_site_id})
539
+
540
+
541
+ @amazon_product_app.command("review")
542
+ def amazon_product_review(sku_id: Annotated[str, typer.Argument(help="SKU ID")], web_site_id: Annotated[Optional[str], typer.Option("--web-site-id", "--site")] = None, current: CurrentOpt = 1, size: SizeOpt = 100) -> None:
543
+ _run_tool("amazon", "amz_review_query", {"skuId": sku_id, "webSiteId": web_site_id, "current": current, "size": size})
544
+
545
+
546
+ @amazon_product_app.command("price")
547
+ def amazon_product_price(node_id: Annotated[str, typer.Argument(help="类目节点 ID")], web_site_id: Annotated[Optional[str], typer.Option("--web-site-id", "--site")] = None, top_n: Annotated[int, typer.Option("--top-n")] = 10, ds: DateOpt = None) -> None:
548
+ _run_tool("amazon", "amz_market_price", {"nodeId": node_id, "webSiteId": web_site_id, "topN": top_n, "ds": ds})
549
+
550
+
551
+ @amazon_hot_app.command("list")
552
+ def amazon_hot_list(web_site_id: Annotated[str, typer.Option("--web-site-id", "--site")], rank_type: Annotated[str, typer.Option("--rank-type")], cat_id: Annotated[Optional[str], typer.Option("--cat-id")] = None, current_page: Annotated[int, typer.Option("--current-page")] = 1, page_size: Annotated[int, typer.Option("--page-size")] = 10) -> None:
553
+ _run_tool("amazon", "amz_hot_amz_hot_list", {"webSiteId": web_site_id, "rankType": rank_type, "catId": cat_id, "currentPage": current_page, "pageSize": page_size})
554
+
555
+
556
+ @amazon_hot_app.command("cat-tree")
557
+ def amazon_hot_cat_tree(web_site_id: Annotated[str, typer.Option("--web-site-id", "--site")], rank_type: Annotated[str, typer.Option("--rank-type")], keyword: Annotated[Optional[str], typer.Option("--keyword")] = None) -> None:
558
+ _run_tool("amazon", "amz_hot_amz_hot_cat_tree", {"webSiteId": web_site_id, "rankType": rank_type, "keyword": keyword})
559
+
560
+
561
+ def main() -> None:
562
+ app()
563
+
564
+
565
+ if __name__ == "__main__":
566
+ main()
proboost_cli/client.py ADDED
@@ -0,0 +1,58 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from proboost_cli.mcp_client import McpClient
6
+
7
+
8
+ class Proboost:
9
+ """Unified Python client for Proboost MCP servers.
10
+
11
+ Usage:
12
+
13
+ from proboost_cli import Proboost
14
+
15
+ pb = Proboost()
16
+ pb.tiktok.tt_video_content(countryRegion="美国", videoId="...")
17
+ pb.patent.patent_query_search(query_text="TACD: virtual reality")
18
+ pb.amazon.amz_product_selection(webSiteId="1")
19
+ """
20
+
21
+ def __init__(
22
+ self,
23
+ *,
24
+ secret_key: str | None = None,
25
+ tiktok_secret_key: str | None = None,
26
+ patent_secret_key: str | None = None,
27
+ amazon_secret_key: str | None = None,
28
+ tiktok_base_url: str | None = None,
29
+ patent_base_url: str | None = None,
30
+ amazon_base_url: str | None = None,
31
+ connect_timeout_sec: float = 30,
32
+ read_timeout_sec: float = 300,
33
+ ) -> None:
34
+ shared: dict[str, Any] = {
35
+ "connect_timeout_sec": connect_timeout_sec,
36
+ "read_timeout_sec": read_timeout_sec,
37
+ }
38
+ self.tiktok = McpClient(
39
+ "tiktok",
40
+ secret_key=tiktok_secret_key or secret_key,
41
+ base_url=tiktok_base_url,
42
+ **shared,
43
+ )
44
+ self.patent = McpClient(
45
+ "patent",
46
+ secret_key=patent_secret_key or secret_key,
47
+ base_url=patent_base_url,
48
+ **shared,
49
+ )
50
+ self.amazon = McpClient(
51
+ "amazon",
52
+ secret_key=amazon_secret_key or secret_key,
53
+ base_url=amazon_base_url,
54
+ **shared,
55
+ )
56
+
57
+ def client(self, mcp: str) -> McpClient:
58
+ return getattr(self, mcp)