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,62 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from typing import Any
5
+
6
+
7
+ def coerce_argument_value(raw: str, prop_schema: dict[str, Any] | None = None) -> Any:
8
+ s = raw.strip()
9
+ schema_type = prop_schema.get("type") if prop_schema else None
10
+
11
+ if schema_type == "integer":
12
+ return int(s, 10)
13
+ if schema_type == "number":
14
+ return float(s)
15
+ if schema_type == "boolean":
16
+ lowered = s.lower()
17
+ if lowered in {"true", "1", "yes", "y"}:
18
+ return True
19
+ if lowered in {"false", "0", "no", "n"}:
20
+ return False
21
+ raise ValueError(f"Invalid boolean value: {raw!r}")
22
+ if schema_type == "array":
23
+ if not s:
24
+ return []
25
+ if s.startswith("["):
26
+ decoded = json.loads(s)
27
+ if not isinstance(decoded, list):
28
+ raise ValueError(f"Expected JSON array: {raw!r}")
29
+ return decoded
30
+ return [part.strip() for part in s.split(",") if part.strip()]
31
+ if schema_type == "string":
32
+ return raw
33
+
34
+ if s.lower() == "null":
35
+ return None
36
+ if s.lower() in {"true", "false"}:
37
+ return s.lower() == "true"
38
+ try:
39
+ return int(s, 10)
40
+ except ValueError:
41
+ pass
42
+ try:
43
+ return float(s)
44
+ except ValueError:
45
+ return raw
46
+
47
+
48
+ def merge_tool_arguments_json(
49
+ base: dict[str, Any],
50
+ sets: tuple[str, ...] | list[str],
51
+ *,
52
+ props: dict[str, Any],
53
+ ) -> dict[str, Any]:
54
+ out = dict(base)
55
+ for item in sets:
56
+ if "=" not in item:
57
+ raise ValueError(f"Invalid argument (expected key=value): {item!r}")
58
+ key, value = item.split("=", 1)
59
+ key = key.strip()
60
+ schema = props.get(key) if isinstance(props.get(key), dict) else {}
61
+ out[key] = coerce_argument_value(value, schema if isinstance(schema, dict) else None)
62
+ return out
proboost_cli/config.py ADDED
@@ -0,0 +1,109 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from proboost_cli import DEFAULT_BASE_URLS, MCP_NAMES
9
+
10
+
11
+ def config_path() -> Path:
12
+ env = os.environ.get("PROBOOST_CONFIG", "").strip()
13
+ if env:
14
+ return Path(env).expanduser()
15
+ xdg = os.environ.get("XDG_CONFIG_HOME", "").strip()
16
+ if xdg:
17
+ return Path(xdg) / "proboost" / "config.json"
18
+ return Path.home() / ".config" / "proboost" / "config.json"
19
+
20
+
21
+ def load_config() -> dict[str, Any]:
22
+ path = config_path()
23
+ if not path.is_file():
24
+ return {}
25
+ text = path.read_text(encoding="utf-8").strip()
26
+ if not text:
27
+ return {}
28
+ return json.loads(text)
29
+
30
+
31
+ def save_config(data: dict[str, Any]) -> None:
32
+ path = config_path()
33
+ path.parent.mkdir(parents=True, exist_ok=True)
34
+ path.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
35
+
36
+
37
+ def normalize_mcp_name(name: str) -> str:
38
+ mcp = name.strip().lower()
39
+ if mcp not in MCP_NAMES:
40
+ raise ValueError(f"Unknown MCP: {name}")
41
+ return mcp
42
+
43
+
44
+ def _env_prefix(mcp: str) -> str:
45
+ return f"PROBOOST_{mcp.upper()}"
46
+
47
+
48
+ def effective_secret_and_base(
49
+ mcp: str,
50
+ *,
51
+ secret_key: str | None = None,
52
+ base_url: str | None = None,
53
+ ) -> tuple[str, str]:
54
+ mcp = normalize_mcp_name(mcp)
55
+ cfg = load_config()
56
+ mcp_cfg = cfg.get("mcps", {}).get(mcp, {}) if isinstance(cfg.get("mcps"), dict) else {}
57
+ if not isinstance(mcp_cfg, dict):
58
+ mcp_cfg = {}
59
+
60
+ prefix = _env_prefix(mcp)
61
+ secret = (
62
+ secret_key
63
+ or os.environ.get(f"{prefix}_SECRET_KEY", "").strip()
64
+ or os.environ.get("PROBOOST_SECRET_KEY", "").strip()
65
+ or str(mcp_cfg.get("secret_key", "")).strip()
66
+ or str(cfg.get("secret_key", "")).strip()
67
+ )
68
+ base = (
69
+ base_url
70
+ or os.environ.get(f"{prefix}_BASE_URL", "").strip()
71
+ or str(mcp_cfg.get("base_url", "")).strip()
72
+ or DEFAULT_BASE_URLS[mcp]
73
+ ).rstrip("/")
74
+ return secret, base
75
+
76
+
77
+ def update_config(
78
+ *,
79
+ mcp: str | None = None,
80
+ secret_key: str | None = None,
81
+ base_url: str | None = None,
82
+ ) -> dict[str, Any]:
83
+ data = load_config()
84
+ if mcp:
85
+ mcp = normalize_mcp_name(mcp)
86
+ data.setdefault("mcps", {})
87
+ if not isinstance(data["mcps"], dict):
88
+ data["mcps"] = {}
89
+ data["mcps"].setdefault(mcp, {})
90
+ if secret_key is not None:
91
+ data["mcps"][mcp]["secret_key"] = secret_key.strip()
92
+ if base_url is not None:
93
+ data["mcps"][mcp]["base_url"] = base_url.strip().rstrip("/")
94
+ else:
95
+ if secret_key is not None:
96
+ data["secret_key"] = secret_key.strip()
97
+ if base_url is not None:
98
+ data["base_url"] = base_url.strip().rstrip("/")
99
+ save_config(data)
100
+ return data
101
+
102
+
103
+ def mask_secret(secret: str) -> str:
104
+ secret = secret.strip()
105
+ if not secret:
106
+ return "(empty)"
107
+ if len(secret) <= 8:
108
+ return "*" * len(secret)
109
+ return "*" * (len(secret) - 4) + secret[-4:]
@@ -0,0 +1,113 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from datetime import timedelta
5
+ from typing import Any
6
+
7
+ import httpx
8
+ from mcp import ClientSession
9
+ from mcp import types as mtypes
10
+ from mcp.client.streamable_http import streamable_http_client
11
+ from mcp.shared._httpx_utils import create_mcp_http_client
12
+
13
+ from proboost_cli.config import effective_secret_and_base, normalize_mcp_name
14
+ from proboost_cli.parse_result import decode_embedded_payload
15
+ from proboost_cli.schemas_loader import load_tool_definitions
16
+
17
+
18
+ def extract_result_text(result: mtypes.CallToolResult) -> str:
19
+ chunks: list[str] = []
20
+ for block in result.content:
21
+ if isinstance(block, mtypes.TextContent):
22
+ chunks.append(block.text)
23
+ return "\n".join(chunks)
24
+
25
+
26
+ async def invoke_tool_async(
27
+ base_url: str,
28
+ secret_key: str,
29
+ tool_name: str,
30
+ arguments: dict[str, Any],
31
+ *,
32
+ connect_timeout_sec: float = 30,
33
+ read_timeout_sec: float = 300,
34
+ ) -> mtypes.CallToolResult:
35
+ headers = {"secret-key": secret_key}
36
+ timeout = httpx.Timeout(connect_timeout_sec, read=read_timeout_sec, write=30, pool=30)
37
+
38
+ async with create_mcp_http_client(headers=headers, timeout=timeout) as client:
39
+ async with streamable_http_client(base_url, http_client=client) as (read, write, _get_id):
40
+ async with ClientSession(read, write) as session:
41
+ await session.initialize()
42
+ return await session.call_tool(
43
+ tool_name,
44
+ arguments,
45
+ read_timeout_seconds=timedelta(seconds=read_timeout_sec),
46
+ )
47
+
48
+
49
+ class McpClient:
50
+ """Dynamic Python client for one Proboost MCP server."""
51
+
52
+ def __init__(
53
+ self,
54
+ mcp: str,
55
+ *,
56
+ secret_key: str | None = None,
57
+ base_url: str | None = None,
58
+ connect_timeout_sec: float = 30,
59
+ read_timeout_sec: float = 300,
60
+ ) -> None:
61
+ self.mcp = normalize_mcp_name(mcp)
62
+ resolved_secret, resolved_base = effective_secret_and_base(
63
+ self.mcp,
64
+ secret_key=secret_key,
65
+ base_url=base_url,
66
+ )
67
+ if not resolved_secret:
68
+ raise ValueError(
69
+ f"缺少 {self.mcp} secret-key。请设置 PROBOOST_SECRET_KEY 或 PROBOOST_{self.mcp.upper()}_SECRET_KEY。"
70
+ )
71
+ self.secret_key = resolved_secret
72
+ self.base_url = resolved_base
73
+ self.connect_timeout_sec = connect_timeout_sec
74
+ self.read_timeout_sec = read_timeout_sec
75
+ self._tools = load_tool_definitions(self.mcp)
76
+
77
+ async def call_tool_async(self, tool_name: str, arguments: dict[str, Any] | None = None) -> Any:
78
+ if tool_name not in self._tools:
79
+ raise ValueError(f"未知工具: {self.mcp}.{tool_name}")
80
+ result = await invoke_tool_async(
81
+ self.base_url,
82
+ self.secret_key,
83
+ tool_name,
84
+ dict(arguments or {}),
85
+ connect_timeout_sec=self.connect_timeout_sec,
86
+ read_timeout_sec=self.read_timeout_sec,
87
+ )
88
+ if result.isError:
89
+ text = extract_result_text(result)
90
+ raise RuntimeError(text or f"{self.mcp}.{tool_name} returned an MCP error")
91
+
92
+ structured = getattr(result, "structuredContent", None)
93
+ if isinstance(structured, (dict, list)):
94
+ return structured
95
+
96
+ text = extract_result_text(result)
97
+ try:
98
+ return decode_embedded_payload(text)
99
+ except ValueError:
100
+ return text
101
+
102
+ def call_tool(self, tool_name: str, arguments: dict[str, Any] | None = None) -> Any:
103
+ return asyncio.run(self.call_tool_async(tool_name, arguments))
104
+
105
+ def __getattr__(self, name: str):
106
+ if name in self._tools:
107
+ def method(**kwargs: Any) -> Any:
108
+ return self.call_tool(name, kwargs)
109
+
110
+ method.__name__ = name
111
+ method.__doc__ = str(self._tools[name].get("description", ""))
112
+ return method
113
+ raise AttributeError(name)
@@ -0,0 +1,53 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import re
5
+ from typing import Any
6
+
7
+ _JSON_FENCE_RE = re.compile(r"```(?:json)?\s*([\s\S]*?)```", re.IGNORECASE)
8
+ _MARKER = "响应数据(JSON)"
9
+
10
+
11
+ def _last_longest_json_value(text: str) -> tuple[Any | None, int]:
12
+ decoder = json.JSONDecoder()
13
+ best: Any | None = None
14
+ best_len = -1
15
+ for idx, ch in enumerate(text):
16
+ if ch not in "{[":
17
+ continue
18
+ try:
19
+ obj, end_idx = decoder.raw_decode(text[idx:])
20
+ except json.JSONDecodeError:
21
+ continue
22
+ if end_idx >= best_len:
23
+ best = obj
24
+ best_len = end_idx
25
+ return best, best_len
26
+
27
+
28
+ def decode_embedded_payload(text: str) -> Any:
29
+ stripped = text.strip()
30
+ try:
31
+ return json.loads(stripped)
32
+ except json.JSONDecodeError:
33
+ pass
34
+
35
+ marker_idx = text.find(_MARKER)
36
+ if marker_idx >= 0:
37
+ tail = text[marker_idx + len(_MARKER) :].lstrip("\n:")
38
+ try:
39
+ return decode_embedded_payload(tail)
40
+ except ValueError:
41
+ pass
42
+
43
+ for match in _JSON_FENCE_RE.finditer(text):
44
+ block = match.group(1).strip()
45
+ try:
46
+ return json.loads(block)
47
+ except json.JSONDecodeError:
48
+ continue
49
+
50
+ best, length = _last_longest_json_value(text)
51
+ if best is None or length < 2:
52
+ raise ValueError("No JSON object found in tool output")
53
+ return best
@@ -0,0 +1,112 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from proboost_cli import MCP_LABELS, MCP_NAMES
6
+ from proboost_cli.schemas_loader import load_tool_definitions, tool_schema_required
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class ToolMeta:
11
+ name: str
12
+ mcp: str
13
+ domain: str
14
+ label: str
15
+ required: list[str]
16
+ paginated: bool = False
17
+
18
+
19
+ DOMAIN_LABELS: dict[str, dict[str, str]] = {
20
+ "tiktok": {
21
+ "statistics": "大盘统计",
22
+ "commodity": "商品选品",
23
+ "expert": "达人分析",
24
+ "video": "视频分析",
25
+ "shop": "店铺分析",
26
+ "other": "其它",
27
+ },
28
+ "patent": {
29
+ "search": "专利检索",
30
+ "detail": "专利详情",
31
+ "image": "图像检索",
32
+ "other": "其它",
33
+ },
34
+ "amazon": {
35
+ "product": "商品与市场",
36
+ "hot": "榜单",
37
+ "other": "其它",
38
+ },
39
+ }
40
+
41
+
42
+ def domain_for(mcp: str, tool_name: str) -> str:
43
+ if mcp == "tiktok":
44
+ if tool_name.startswith("tt_commodity_statistics_"):
45
+ return "statistics"
46
+ if tool_name.startswith(("tt_commodity_", "tt_sale_channel_")):
47
+ return "commodity"
48
+ if tool_name.startswith(("tt_expert_", "tk_expert_")):
49
+ return "expert"
50
+ if tool_name.startswith("tt_video_"):
51
+ return "video"
52
+ if tool_name.startswith("tt_shop_"):
53
+ return "shop"
54
+ return "other"
55
+ if mcp == "patent":
56
+ if "search" in tool_name or tool_name == "patent_query_count":
57
+ return "search"
58
+ if "image_search" in tool_name:
59
+ return "image"
60
+ return "detail"
61
+ if mcp == "amazon":
62
+ if tool_name.startswith("amz_hot_"):
63
+ return "hot"
64
+ return "product"
65
+ return "other"
66
+
67
+
68
+ def _label_from_description(description: str, fallback: str) -> str:
69
+ desc = " ".join(description.split())
70
+ if not desc:
71
+ return fallback
72
+ for sep in (":", ";", ";", "。", "."):
73
+ if sep in desc:
74
+ desc = desc.split(sep, 1)[0]
75
+ break
76
+ return desc[:50]
77
+
78
+
79
+ def _is_paginated(tool: dict) -> bool:
80
+ props = tool.get("arguments", {}).get("properties", {})
81
+ if not isinstance(props, dict):
82
+ return False
83
+ return any(key in props for key in ("current", "size", "limit", "offset", "pages", "pagesSize", "currentPage", "pageSize"))
84
+
85
+
86
+ def build_tools() -> dict[str, dict[str, ToolMeta]]:
87
+ out: dict[str, dict[str, ToolMeta]] = {}
88
+ for mcp in MCP_NAMES:
89
+ out[mcp] = {}
90
+ for name, tool in load_tool_definitions(mcp).items():
91
+ out[mcp][name] = ToolMeta(
92
+ name=name,
93
+ mcp=mcp,
94
+ domain=domain_for(mcp, name),
95
+ label=_label_from_description(str(tool.get("description", "")), name),
96
+ required=tool_schema_required(tool),
97
+ paginated=_is_paginated(tool),
98
+ )
99
+ return out
100
+
101
+
102
+ TOOLS: dict[str, dict[str, ToolMeta]] = build_tools()
103
+
104
+ DOMAIN_TOOLS: dict[str, dict[str, list[str]]] = {}
105
+ for _mcp in MCP_NAMES:
106
+ DOMAIN_TOOLS[_mcp] = {}
107
+ for _name, _meta in sorted(TOOLS[_mcp].items()):
108
+ DOMAIN_TOOLS[_mcp].setdefault(_meta.domain, []).append(_name)
109
+
110
+
111
+ def mcp_title(mcp: str) -> str:
112
+ return MCP_LABELS.get(mcp, mcp)
@@ -0,0 +1 @@
1
+ """Bundled MCP tool schemas for Proboost servers."""
@@ -0,0 +1 @@
1
+ """Amazon MCP tool manifests."""
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "amz_category_query",
3
+ "description": "按分区日期与类目 ID 查询 AMZ 类目信息(层级、父子、路径等,Hologres big_prd / api_holo);可选 webSiteId 限定站点",
4
+ "arguments": {
5
+ "type": "object",
6
+ "properties": {
7
+ "catId": {
8
+ "description": "类目 ID",
9
+ "type": "string"
10
+ },
11
+ "webSiteId": {
12
+ "description": "站点 ID,可选;为空则查所有站点",
13
+ "type": "string"
14
+ }
15
+ },
16
+ "required": [
17
+ "catId"
18
+ ]
19
+ }
20
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "amz_hot_amz_hot_cat_tree",
3
+ "description": "AMZ 榜单可用商品类目树(多棵顶层树),与 `amzHotList` 等联用做类目筛选。",
4
+ "arguments": {
5
+ "type": "object",
6
+ "properties": {
7
+ "keyword": {
8
+ "description": "英文类目名,精确匹配;有值时返回包含该节点及父级的子树",
9
+ "type": "string"
10
+ },
11
+ "rankType": {
12
+ "description": "榜单类型(如 Best Seller、New Releases、Movers Shakers)",
13
+ "type": "string"
14
+ },
15
+ "webSiteId": {
16
+ "description": "站点 ID",
17
+ "type": "string"
18
+ }
19
+ },
20
+ "required": [
21
+ "rankType",
22
+ "webSiteId"
23
+ ]
24
+ }
25
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "amz_hot_amz_hot_list",
3
+ "description": "AMZ 榜单 SKU 分页:类目路径、排名、主图与链接、品牌、近 30 天销量/销售额、售价与币种、销量/价格趋势、是否收藏等。",
4
+ "arguments": {
5
+ "type": "object",
6
+ "properties": {
7
+ "catId": {
8
+ "description": "类目 ID;可空,空时默认 fashion",
9
+ "type": "string"
10
+ },
11
+ "currentPage": {
12
+ "description": "当前页,默认 1",
13
+ "type": "integer"
14
+ },
15
+ "pageSize": {
16
+ "description": "每页条数,默认 10",
17
+ "type": "integer"
18
+ },
19
+ "rankType": {
20
+ "description": "榜单类型(如 Best Seller、New Releases、Movers Shakers)",
21
+ "type": "string"
22
+ },
23
+ "webSiteId": {
24
+ "description": "站点 ID",
25
+ "type": "string"
26
+ }
27
+ },
28
+ "required": [
29
+ "webSiteId",
30
+ "rankType"
31
+ ]
32
+ }
33
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "amz_market_price",
3
+ "description": "查询站点内销量 TopN SKU 的价格带分布(区间销量、销售额、SKU 列表及销量占比);nodeId(cat_id)必填;可选月份 ds 或近30天",
4
+ "arguments": {
5
+ "type": "object",
6
+ "properties": {
7
+ "ds": {
8
+ "description": "统计月份,6 位 YYYYMM(如 202603)表示按月历史口径;不传则近 30 天口径",
9
+ "type": "string"
10
+ },
11
+ "nodeId": {
12
+ "description": "类目节点 ID(必填),对应 cat_id;仅该类目下取 TopN(mi 表既有列,df 表需已新增 cat_id)",
13
+ "type": "string"
14
+ },
15
+ "topN": {
16
+ "description": "按销量取前 N 个 SKU 再按价格带聚合,默认 10,最大 500",
17
+ "type": "integer"
18
+ },
19
+ "webSiteId": {
20
+ "description": "站点 ID;不传或空则默认 1(美国)。常用:1=us,2=gb,3=de,4=fr,5=jp,6=ca,8=it,9=es,10=in,11=mx,12=au,13=br,17=pl,21=ae,22=sa,23=eg",
21
+ "type": "string"
22
+ }
23
+ },
24
+ "required": [
25
+ "nodeId"
26
+ ]
27
+ }
28
+ }