@scorehub/creator-outreach 1.0.8 → 1.0.11
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.
|
@@ -32,15 +32,17 @@ skills:
|
|
|
32
32
|
### 1. 达人搜索与发现
|
|
33
33
|
在多维筛选条件下,从 TikTok Creator Marketplace 精准搜索达人。
|
|
34
34
|
- **类目筛选(父类目优先,务必遵守)**:
|
|
35
|
-
1. 当用户提到品类/类目关键词(如"美妆"/"3C"/"beauty"/"electronics")时,**先调用 `get_categories`** 解析为类目 ID。传入 `keyword`(品类名),并根据语言设置 `locale`(中文用 `"zh-CN"`,英文用 `"en-US"
|
|
36
|
-
2. **优先匹配父类目(`is_leaf=false`)**:从返回结果中筛选 `is_leaf=false`
|
|
35
|
+
1. 当用户提到品类/类目关键词(如"美妆"/"3C"/"beauty"/"electronics")时,**先调用 `get_categories`** 解析为类目 ID。传入 `keyword`(品类名),并根据语言设置 `locale`(中文用 `"zh-CN"`,英文用 `"en-US"`)。`category_version` 跟随区域:US/EU/SEA 用 `"v2"`(官方推荐),其余区域按返回情况定,不必硬性钦定版本。
|
|
36
|
+
2. **优先匹配父类目(`is_leaf=false`)**:从返回结果中筛选 `is_leaf=false` 的父类目。匹配到父类目:
|
|
37
37
|
- 构造 `category` 参数:`[{parent_category_id: "父类目ID"}]` —— **不要传 `child_category_id_list`**。API 会搜索该父类目下所有子类目的达人,覆盖面最广。
|
|
38
38
|
- 如果匹配到多个父类目,用表格展示让用户确认想要筛选哪些(或全部)。
|
|
39
|
-
3.
|
|
40
|
-
-
|
|
41
|
-
- 构造 `category` 参数:`[{parent_category_id: "父类目ID", child_category_id_list: ["
|
|
39
|
+
3. **兜底匹配子类目(仅限直接下一级)**:仅当没有合适父类目时降级:
|
|
40
|
+
- 只取某父类目**直接下一级**的子类目(其 `parent_id` 等于该父类目 `id`),**绝不用更深层的叶子 ID**。
|
|
41
|
+
- 构造 `category` 参数:`[{parent_category_id: "父类目ID", child_category_id_list: ["直接子类ID1", "直接子类ID2"]}]`。
|
|
42
42
|
- 如果匹配到多个子类目,同样用表格展示让用户确认。
|
|
43
|
-
-
|
|
43
|
+
- **关键约束**:达人搜索的 `category` 只支持「父类目 + 直接下一级子类目」(TikTok 官方限制)。v2 是最深 7 级的类目树,直接取深层叶子 ID 会被搜索接口**静默忽略**、导致过滤失效返回一堆无关达人。这就是"中文类目树 ID 与搜索返回 `category_ids` 对不上"的根因。
|
|
44
|
+
- **搜索后闭环校验**:用了类目过滤就要把返回结果的 `category_ids` 与所传类目 ID 比对,**零重叠**说明过滤已静默失效——提示用户并把所传子类目换成其**父类目**(更浅一级)重搜,不要把无关结果当作合规品类结果。
|
|
45
|
+
- **核心原则**:父类目覆盖 > 子类目精确。宁愿多搜到一些达人(由后续 GMV/粉丝画像等条件进一步筛选),也不要漏掉。
|
|
44
46
|
- **注意**:绝不要凭空猜测类目 ID。
|
|
45
47
|
- GMV 区间筛选(0-100 / 100-1K / 1K-10K / 10K+)
|
|
46
48
|
- 销量区间筛选
|
|
@@ -110,6 +112,7 @@ Step 3: 批量建联
|
|
|
110
112
|
|
|
111
113
|
## 限制与注意事项
|
|
112
114
|
|
|
115
|
+
- **支持市场**:当前仅支持东南亚市场——泰国(TH)、马来西亚(MY)、越南(VN)、菲律宾(PH)、印度尼西亚(ID)、新加坡(SG)。用户明确提到其他市场(如美国、日本、欧洲等)时,主动说明不支持,并建议改选以上 SEA 市场之一
|
|
113
116
|
- **日配额**:搜索和表现查询共享每日 10,000 次调用限额
|
|
114
117
|
- **单页限制**:搜索接口每次最多返回 20 条,大批量搜索需要多次翻页
|
|
115
118
|
- **建联门槛**:店铺 GMV 需达标,且店铺与达人需在同一区域
|
package/install.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const os = require('os');
|
|
6
|
+
const { execSync } = require('child_process');
|
|
6
7
|
|
|
7
8
|
const pkg = require('./package.json');
|
|
8
9
|
console.log(`${pkg.name} v${pkg.version}\n`);
|
|
@@ -50,6 +51,22 @@ function resolveWorkBuddyNode() {
|
|
|
50
51
|
return { npx: 'npx', binDir: null };
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
// 工具函数:查询 mcp-server 在 npm registry 的当前最新版本
|
|
55
|
+
// 注意:这是 install 此刻的最新版;实际 MCP 拉起时由 npx 现场解析 @latest,
|
|
56
|
+
// 若之后 registry 更新,运行时版本可能与此不同。失败时静默返回 null(不阻断安装)。
|
|
57
|
+
function fetchLatestMcpServerVersion() {
|
|
58
|
+
try {
|
|
59
|
+
const out = execSync('npm view @scorehub/mcp-server version', {
|
|
60
|
+
encoding: 'utf8',
|
|
61
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
62
|
+
timeout: 10000,
|
|
63
|
+
});
|
|
64
|
+
return out.trim() || null;
|
|
65
|
+
} catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
53
70
|
// 工具函数:合并 MCP Server 配置到 JSON 文件
|
|
54
71
|
function mergeMcpConfig(configPath, serverName, serverConfig, extraFields) {
|
|
55
72
|
let config = {};
|
|
@@ -126,6 +143,13 @@ if (hasWorkBuddy) {
|
|
|
126
143
|
const platforms = ['Claude Code'];
|
|
127
144
|
if (hasWorkBuddy) platforms.push('WorkBuddy');
|
|
128
145
|
|
|
146
|
+
const mcpServerVersion = fetchLatestMcpServerVersion();
|
|
147
|
+
if (mcpServerVersion) {
|
|
148
|
+
console.log(`\n→ mcp-server 当前最新版本:v${mcpServerVersion}(运行时由 npx 解析 @latest,届时可能更新)`);
|
|
149
|
+
} else {
|
|
150
|
+
console.log('\n→ mcp-server:@latest(运行时由 npx 解析,未能查询到具体版本号)');
|
|
151
|
+
}
|
|
152
|
+
|
|
129
153
|
console.log(`\n安装完成!重启 ${platforms.join(' / ')} 后使用 Tiky。`);
|
|
130
154
|
if (!hasWorkBuddy) {
|
|
131
155
|
console.log('提示:未检测到 WorkBuddy,仅安装了 Claude Code。如需 WorkBuddy 支持请先安装 WorkBuddy 后重新运行本命令。');
|
package/package.json
CHANGED
|
@@ -21,12 +21,14 @@
|
|
|
21
21
|
### 3. 粉丝规模(15%)
|
|
22
22
|
- 维度分 = round(min(followers / maxFollowers × 100, 100))
|
|
23
23
|
|
|
24
|
-
### 4. 品类匹配(20
|
|
25
|
-
-
|
|
26
|
-
- 若有 `category_gmv_distribution
|
|
27
|
-
-
|
|
28
|
-
|
|
29
|
-
-
|
|
24
|
+
### 4. 品类匹配(20%)
|
|
25
|
+
- **目标类目 ID 集合**(`target_cat_ids`):由用户品类意图决定,通过 `get_categories` 获取后传入。**默认使用父类目 ID**;仅当用户明确指定某个子类目时,才改用该子类目 ID。示例(美妆父类目):`{600024, 601303, 602118, 602284, 601450, 600025, 600026}`
|
|
26
|
+
- 若有 `category_gmv_distribution`(API 返回的 `category_id` 为**顶级父类目 ID**,与 `target_cat_ids` 同层级):
|
|
27
|
+
- 取 GMV 占比最高的类目,判断其 `category_id` 是否在 `target_cat_ids` 中
|
|
28
|
+
- **命中**(最理想路径)→ 维度分 = round(该类目 GMV 占比 × 100),上限 100
|
|
29
|
+
- **未命中** → 计算分布中所有类目与 `target_cat_ids` 的交集:overlap = 交集类目数 / 分布中类目总数;维度分 = round(overlap × 100)
|
|
30
|
+
- 若无 `category_gmv_distribution` → 维度分记 **null**(标注「品类数据未授权」),不参与品类维度排序
|
|
31
|
+
- **禁止用 `category_ids` 替代**:该字段是达人发帖产品的**叶子类目 ID**,层级远深于父类目,与 `target_cat_ids`(父类目)直接求交集结果恒为空,会错误地将匹配达人评为零分
|
|
30
32
|
|
|
31
33
|
### 5. 粉丝质量(10%,示例以美妆女性受众为基准)
|
|
32
34
|
- 女性占比分 = min(female_pct × 100 / 0.8, 100)(female_pct 为小数,>1 时按 /100 归一)
|
|
@@ -47,28 +49,27 @@
|
|
|
47
49
|
|
|
48
50
|
## MCP 字段映射说明(重要)
|
|
49
51
|
|
|
50
|
-
MCP 工具 `creator_performance` 直接透传 TikTok
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
|
54
|
-
|
|
55
|
-
| `gmv.amount
|
|
56
|
-
| `units_sold` |
|
|
57
|
-
|
|
|
58
|
-
|
|
|
59
|
-
| `ec_live_engagement_rate` | `
|
|
60
|
-
| `ec_video_engagement_rate` | `
|
|
61
|
-
|
|
|
62
|
-
|
|
|
63
|
-
|
|
|
64
|
-
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
4. `category_gmv_distribution` / `category_ids` 缺失 → 用 `top_categories` 的 share 计算占比/重叠
|
|
52
|
+
MCP 工具 `creator_performance` 直接透传 TikTok 详情接口的原始 JSON 响应(见官方 API 文档 `Get Marketplace Creator Performance`)。
|
|
53
|
+
所有评分所需字段位于 `data.creator.*` 路径下,字段对应关系如下:
|
|
54
|
+
|
|
55
|
+
| 评分所需 | MCP 返回字段(`data.creator.*`) | 类型 | 使用方式 |
|
|
56
|
+
|---------|------------------------------|------|---------|
|
|
57
|
+
| GMV | `gmv.amount` | string(如 `"3434.23"`) | `parseFloat()` 后使用 |
|
|
58
|
+
| 销量 | `units_sold` | int | 直接用 |
|
|
59
|
+
| 直播观看 | `avg_ec_live_view_count` | int | 直接用 |
|
|
60
|
+
| 视频播放 | `avg_ec_video_play_count` | int | 直接用 |
|
|
61
|
+
| 直播互动率 | `ec_live_engagement_rate` | string(如 `"6000"` = 60%) | `parseFloat() / 100` 换算为百分比小数 |
|
|
62
|
+
| 视频互动率 | `ec_video_engagement_rate` | string(如 `"3000"` = 30%) | `parseFloat() / 100` 换算为百分比小数 |
|
|
63
|
+
| 粉丝性别 | `follower_gender[].key` / `.value` | key 为 string(`"Male"` / `"Female"`,首字母大写),value 为 string 小数(如 `"0.5000"`) | `parseFloat(value)` 后使用;key 比较需兼容大小写 |
|
|
64
|
+
| 粉丝年龄 | `follower_age[].key` / `.value` | key 为 string(如 `"18-23"`),value 为 string 小数(如 `"0.2500"`) | `parseFloat(value)` 后使用 |
|
|
65
|
+
| 品类 GMV 分布 | `category_gmv_distribution[].category_id` / `.value` | category_id 为 string(**顶级父类目 ID**),value 为 string 小数(如 `"0.3035"`) | `parseFloat(value)` 后作为 GMV 占比 |
|
|
66
|
+
| 品类列表 | `category_ids` | string[](**叶子类目 ID**) | **不用于品类匹配**(与父类目 ID 层级不同,交集恒为空) |
|
|
67
|
+
|
|
68
|
+
**执行建议**:优先使用上表字段代入完整公式;若某字段缺失(接口未返回),对应子维度记 0 或 null 并**标注数据缺口**:
|
|
69
|
+
- `avg_ec_live_view_count` 或 `avg_ec_video_play_count` 缺失 → 对应观看子分记 0,注明数据缺口
|
|
70
|
+
- `ec_live_engagement_rate` 或 `ec_video_engagement_rate` 缺失 → 对应互动子分记 0,注明数据缺口
|
|
71
|
+
- `follower_gender` 或 `follower_age` 缺失 → 对应粉丝质量子分记 0,注明数据缺口
|
|
72
|
+
- `category_gmv_distribution` 缺失 → 品类匹配维度记 **null**(不参与排序),注明「品类数据未授权」
|
|
72
73
|
|
|
73
74
|
字段缺失时对应维度**标注数据缺口**,不要静默填 0 造成排序失真。
|
|
74
75
|
|
|
@@ -107,24 +108,23 @@ def score_creator(c, all_creators):
|
|
|
107
108
|
scores["followers"] = round(min((c.get("follower_count", 0) or 0) / max_followers * 100, 100))
|
|
108
109
|
|
|
109
110
|
# 4. 品类匹配 (20%)
|
|
110
|
-
|
|
111
|
-
categories = c.get("category_ids", [])
|
|
111
|
+
target_cat_ids = {"600024", "601303", "602118", "602284", "601450", "600025", "600026"} # 应由 get_categories 获取,示例为美妆父类目
|
|
112
112
|
cat_gmv_dist = c.get("category_gmv_distribution", [])
|
|
113
113
|
if cat_gmv_dist:
|
|
114
114
|
top_cat = max(cat_gmv_dist, key=lambda x: float(x.get("value", 0) or 0))
|
|
115
|
-
if top_cat.get("category_id", "") in
|
|
116
|
-
scores["category"] = round(float(top_cat.get("value", 0) or 0) * 100)
|
|
115
|
+
if top_cat.get("category_id", "") in target_cat_ids:
|
|
116
|
+
scores["category"] = round(min(float(top_cat.get("value", 0) or 0) * 100, 100))
|
|
117
117
|
else:
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
dist_ids = {x.get("category_id", "") for x in cat_gmv_dist}
|
|
119
|
+
overlap = len(dist_ids & target_cat_ids) / max(len(dist_ids), 1)
|
|
120
|
+
scores["category"] = round(overlap * 100)
|
|
120
121
|
else:
|
|
121
|
-
|
|
122
|
-
scores["category"] = round(overlap * 100)
|
|
122
|
+
scores["category"] = None # 品类数据未授权,不参与排序
|
|
123
123
|
|
|
124
124
|
# 5. 粉丝质量 (10%)
|
|
125
125
|
female_pct = 0
|
|
126
126
|
for g in c.get("follower_gender", []):
|
|
127
|
-
if g.get("key") == "female":
|
|
127
|
+
if g.get("key", "").lower() == "female":
|
|
128
128
|
female_pct = float(g.get("value", 0) or 0)
|
|
129
129
|
q = min((female_pct if female_pct <= 1 else female_pct / 100) * 100 / 0.8, 100)
|
|
130
130
|
core_age = sum(float(a.get("value", 0) or 0) for a in c.get("follower_age", [])
|
|
@@ -133,7 +133,7 @@ def score_creator(c, all_creators):
|
|
|
133
133
|
|
|
134
134
|
scores["total"] = round(
|
|
135
135
|
scores["sales"] * 0.30 + scores["content"] * 0.25 + scores["followers"] * 0.15
|
|
136
|
-
+ scores["category"] * 0.20 + scores["follower_quality"] * 0.10
|
|
136
|
+
+ (scores["category"] or 0) * 0.20 + scores["follower_quality"] * 0.10
|
|
137
137
|
)
|
|
138
138
|
return scores
|
|
139
139
|
```
|
|
@@ -17,18 +17,19 @@
|
|
|
17
17
|
|
|
18
18
|
如果用户提到了品类/类目关键词(如"美妆"、"3C"、"beauty"、"electronics"),**必须**先调用 `get_categories` 获取类目 ID,再调用 `search_creators`:
|
|
19
19
|
|
|
20
|
-
1. **`get_categories`** — 传入 `keyword
|
|
21
|
-
|
|
20
|
+
1. **`get_categories`** — 传入 `keyword`(品类名关键词),并设合适的 `locale`(中文用 `"zh-CN"`,英文用 `"en-US"`)。`category_version` 跟随区域:US/EU/SEA 用 `"v2"`(官方推荐,类目树更完整),其余区域按返回情况定,不必硬性钦定某个版本。
|
|
21
|
+
- **关键层级约束(与版本无关)**:达人搜索的 `category` 过滤**只认父类目 + 其直接下一级子类目**(TikTok 官方约束:"A child category is only 1 level deeper than the parent")。类目树最深可达 7 级,若直接取深层子类目 ID 喂给搜索接口会被**静默忽略**,导致过滤失效、返回一堆无关达人(这正是"中文树 ID 和搜索返回 category_ids 对不上"的根因)。因此重点不在选哪个版本,而在**只用父类目或其直接下一级**。
|
|
22
|
+
2. **优先匹配父类目** — 从返回的 `categories` 中筛 `is_leaf=false` 的父类目。匹配到就直接用:
|
|
22
23
|
- 构造 `category` 参数时 **只传 `parent_category_id`,不传 `child_category_id_list`**:`[{parent_category_id: "父ID"}]`
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
3.
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
24
|
+
- API 会搜索该父类目下**所有**子类目的达人,覆盖面最广
|
|
25
|
+
- 匹配到多个父类目,用**表格展示**让用户确认要筛选哪些
|
|
26
|
+
3. **兜底匹配子类目(仅限父类目的直接下一级)** — 仅当没有合适父类目时降级:
|
|
27
|
+
- 只取某父类目**直接下一级**的子类目(`parent_id` 等于该父类目 `id`),**绝不使用更深层的叶子 ID**
|
|
28
|
+
- 构造:`[{parent_category_id: "父ID", child_category_id_list: ["直接子类ID1", "直接子类ID2"]}]`
|
|
29
|
+
- 匹配到多个,同样用**表格展示**让用户确认
|
|
29
30
|
4. 将构造好的 `category` 参数传给 `search_creators`
|
|
30
31
|
|
|
31
|
-
> **核心原则**:父类目覆盖 >
|
|
32
|
+
> **核心原则**:父类目覆盖 > 子类目精确。且**层级只能到"父类目 + 直接子类目"**,深层叶子无效。宁愿多搜到一些达人(由后续 GMV/粉丝画像等条件进一步筛选),也不要漏掉。
|
|
32
33
|
|
|
33
34
|
调用 MCP 工具 **`search_creators`**,不要写脚本。若已授权可直接调用;未授权时先走 `authorize`。
|
|
34
35
|
|
|
@@ -37,7 +38,7 @@
|
|
|
37
38
|
| 参数 | 说明 |
|
|
38
39
|
|------|------|
|
|
39
40
|
| `keyword` | 用户名/昵称搜索(可选) |
|
|
40
|
-
| `category` | 品类筛选数组(可选)。支持两种格式:① **父类目模式(推荐优先使用)**:`[{parent_category_id: "父ID"}]`,不传 `child_category_id_list`,搜索该父类目下所有子类目;②
|
|
41
|
+
| `category` | 品类筛选数组(可选)。支持两种格式:① **父类目模式(推荐优先使用)**:`[{parent_category_id: "父ID"}]`,不传 `child_category_id_list`,搜索该父类目下所有子类目;② **父+直接子类目模式(仅兜底)**:`[{parent_category_id: "父ID", child_category_id_list: ["直接子类ID1"]}]`,子类目只能比父类目深一级。多个对象 OR 关系,单个对象内 child_category_id_list AND 关系。类目 ID **必须先通过 `get_categories` 获取**,不要硬编码或猜测;**禁止使用深层叶子 ID**。 |
|
|
41
42
|
| `gmv_ranges` | GMV 区间数组,取值见下表(可选) |
|
|
42
43
|
| `page_size` | 每页条数,默认/上限 20 |
|
|
43
44
|
| `page_token` | 翻页令牌,首次不传,用上一页返回的 `next_page_token` 续拉 |
|
|
@@ -50,6 +51,15 @@
|
|
|
50
51
|
- **务必透传每个达人的 `creator_open_id`**——这是后续 `create_conversation` 建联的必需字段,且**仅搜索结果返回**(分析接口不返回)。
|
|
51
52
|
- 单页最多 20 条,大批量搜索需用 `page_token` 多次翻页。
|
|
52
53
|
|
|
54
|
+
### 类目过滤闭环校验(防静默失效)
|
|
55
|
+
|
|
56
|
+
用了 `category` 过滤时,搜索返回后**必须自检**:把返回结果里各达人的 `category_ids` 汇总,与本次传入的 `parent_category_id` / `child_category_id_list` 比对。
|
|
57
|
+
|
|
58
|
+
- 若**零重叠**(返回的 `category_ids` 完全不含所传的类目 ID),说明类目 ID 很可能层级过深或版本不对,**过滤已静默失效**——不要把这批结果当作"符合品类"直接给用户。此时应:
|
|
59
|
+
1. 提示用户"当前类目过滤可能未生效";
|
|
60
|
+
2. 回退到更浅的类目层级重搜:把所传的深层子类目换成其**父类目**(只传 `parent_category_id`)重新 `search_creators`;仍零重叠时再上溯一级到顶级父类目。
|
|
61
|
+
- 若有重叠,说明过滤生效,正常展示。
|
|
62
|
+
|
|
53
63
|
## 降级:直接 API
|
|
54
64
|
|
|
55
65
|
MCP 不可用时,降级用环境变量凭证直接调用:
|
|
@@ -59,7 +69,22 @@ POST https://open-api.tiktokglobalshop.com/affiliate_seller/202508/marketplace_c
|
|
|
59
69
|
```
|
|
60
70
|
|
|
61
71
|
- 签名:HMAC-SHA256,body 为空 `{}` 时不参与签名;compact JSON `separators=(",", ":")`
|
|
62
|
-
- 类目 ID 应通过 `GET /product/202309/categories` 获取,不要硬编码。MCP 可用时用 `get_categories`
|
|
72
|
+
- 类目 ID 应通过 `GET /product/202309/categories` 获取,不要硬编码。MCP 可用时用 `get_categories` 工具。注意达人搜索的 `category` 只认「父类目 + 直接下一级子类目」,深层叶子无效;已知父类目段 ID 形如 `600001`(6 位),可作层级参考。
|
|
73
|
+
|
|
74
|
+
## 市场限制
|
|
75
|
+
|
|
76
|
+
当前仅支持以下东南亚市场:
|
|
77
|
+
|
|
78
|
+
| 国家 | 代码 |
|
|
79
|
+
|------|------|
|
|
80
|
+
| 泰国 | TH |
|
|
81
|
+
| 马来西亚 | MY |
|
|
82
|
+
| 越南 | VN |
|
|
83
|
+
| 菲律宾 | PH |
|
|
84
|
+
| 印度尼西亚 | ID |
|
|
85
|
+
| 新加坡 | SG |
|
|
86
|
+
|
|
87
|
+
用户在搜索前若明确指定目标市场(如"帮我找日本的达人"、"搜索美国市场创作者"),应**立即告知不支持该市场**,并建议用户改选以上 SEA 市场之一,不要直接调用 `search_creators`。
|
|
63
88
|
|
|
64
89
|
## 错误处理
|
|
65
90
|
|