@lotaber_wang/openclaw-dc-plugin 0.1.6 → 0.1.8
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.
- package/README.md +59 -4
- package/index.js +82 -16
- package/lib/mcp-bridge.js +7 -9
- package/openclaw.plugin.json +2 -1
- package/package.json +5 -2
- package/skills/taptap-dc-ops-brief/SKILL.md +4 -4
package/README.md
CHANGED
|
@@ -9,23 +9,51 @@
|
|
|
9
9
|
- 运行时解析顺序为:插件内 bundled runtime -> 本地已安装 runtime -> 本地缓存 runtime
|
|
10
10
|
- 暴露适合 agent / skill 消费的 TapTap DC 原始 JSON 工具
|
|
11
11
|
- 提供高层工具 `taptap_dc_quick_brief`,可直接按游戏名 / app_id 生成 TapTap DC 快速简报
|
|
12
|
-
- 如果当前未授权,`taptap_dc_quick_brief` 和 `taptap_dc_start_authorization`
|
|
12
|
+
- 如果当前未授权,`taptap_dc_quick_brief` 和 `taptap_dc_start_authorization` 都会优先返回“可直接点击的授权链接”,并附带 TapTap 包装链接与授权页直链
|
|
13
13
|
- 覆盖授权、选游戏、商店/评价/社区概览、商店快照、论坛内容、评价列表、点赞与官方回复
|
|
14
14
|
- 内置 `taptap-dc-ops-brief`,可把原始数据整理成简洁的运营简报
|
|
15
15
|
|
|
16
16
|
## 安装
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
openclaw plugins install @lotaber_wang/openclaw-dc-plugin
|
|
19
|
+
openclaw plugins install @lotaber_wang/openclaw-dc-plugin@0.1.8
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
+
安装完成后,建议立刻执行一次检查:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
openclaw plugins inspect taptap-dc-plugin
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
如果安装成功但当前会话里还看不到 `taptap_dc_*` 工具,请重启 Gateway:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
openclaw gateway restart
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
如果你的环境没有 `openclaw gateway restart`,就直接完整重启 OpenClaw / 宿主应用。
|
|
35
|
+
|
|
36
|
+
## 推荐安装流程
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
openclaw plugins install @lotaber_wang/openclaw-dc-plugin@0.1.8
|
|
40
|
+
openclaw plugins inspect taptap-dc-plugin
|
|
41
|
+
openclaw gateway restart
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
说明:
|
|
45
|
+
|
|
46
|
+
- `install` 负责把插件包装进 OpenClaw
|
|
47
|
+
- `inspect` 用来确认插件元信息、skills、tools 是否已被 Gateway 识别
|
|
48
|
+
- `gateway restart` 用来让最新插件配置真正生效;很多“装上了但没看到 tools”的问题都出在这里
|
|
49
|
+
|
|
22
50
|
## 典型使用流程
|
|
23
51
|
|
|
24
52
|
优先推荐直接使用:
|
|
25
53
|
|
|
26
54
|
1. 直接调用 `taptap_dc_quick_brief`
|
|
27
55
|
2. 如果还没授权,工具会直接返回可点击授权链接
|
|
28
|
-
3.
|
|
56
|
+
3. 手机端优先直接点击第一条“直接点击授权”链接;桌面端可打开授权页直链后自行扫码或转发
|
|
29
57
|
4. 完成扫码后调用 `taptap_dc_complete_authorization`
|
|
30
58
|
5. 再次调用 `taptap_dc_quick_brief`
|
|
31
59
|
|
|
@@ -81,17 +109,44 @@ openclaw plugins install @lotaber_wang/openclaw-dc-plugin
|
|
|
81
109
|
|
|
82
110
|
如果 OpenClaw 能安装插件,但第一次调用工具仍然偏慢,通常是在启动内嵌 TapTap 运行时,而不是在现场重新下载依赖。
|
|
83
111
|
|
|
112
|
+
如果安装后看不到 `taptap_dc_*` 工具,优先按这个顺序排查:
|
|
113
|
+
|
|
114
|
+
1. 确认安装的是 `@lotaber_wang/openclaw-dc-plugin@0.1.8` 或更新版本
|
|
115
|
+
2. 执行 `openclaw plugins inspect taptap-dc-plugin`
|
|
116
|
+
3. 执行 `openclaw gateway restart`
|
|
117
|
+
4. 重新开启一个新会话,再观察工具列表
|
|
118
|
+
|
|
119
|
+
如果 `inspect` 能看到插件,但工具仍未生效,通常不是插件代码没注册,而是 Gateway 还没完成配置重载。
|
|
120
|
+
|
|
121
|
+
说明:
|
|
122
|
+
|
|
123
|
+
- 本插件的 `taptap_dc_*` 工具由插件入口代码动态注册
|
|
124
|
+
- `openclaw.plugin.json` 主要承担插件元信息、默认启用和配置校验职责
|
|
125
|
+
- 所以“装完看不到 tool”时,优先怀疑安装状态、Gateway 重启和会话刷新,而不是去手改 manifest 里的 `tools` 字段
|
|
126
|
+
|
|
127
|
+
如果出现配置异常,优先检查插件 ID 是否一致:
|
|
128
|
+
|
|
129
|
+
- package 名称:`@lotaber_wang/openclaw-dc-plugin`
|
|
130
|
+
- plugin id:`taptap-dc-plugin`
|
|
131
|
+
- OpenClaw 配置入口:`plugins.entries.taptap-dc-plugin`
|
|
132
|
+
|
|
84
133
|
如果第一次调用失败,优先检查:
|
|
85
134
|
|
|
86
135
|
- 当前环境是否能访问 npm registry
|
|
87
136
|
- 本机 Node 版本是否 >= 18.14.1
|
|
88
137
|
- OpenClaw 进程是否有临时目录写权限
|
|
138
|
+
- OpenClaw 是否已经完成 Gateway 重启
|
|
89
139
|
|
|
90
|
-
从 `0.1.
|
|
140
|
+
从 `0.1.8` 开始,插件安装链路与 bridge 会额外做这些兼容处理:
|
|
91
141
|
|
|
142
|
+
- 在 `package.json` 中补充 `openclaw.install.npmSpec`,让安装链路更稳定
|
|
143
|
+
- 在 `openclaw.plugin.json` 中显式开启 `enabledByDefault`
|
|
92
144
|
- `initialize` 超时后自动切换到无缓冲 / PTY 启动策略重试
|
|
93
145
|
- `initialize` 默认超时提升到 45 秒,避免宿主启动稍慢时过早失败
|
|
146
|
+
- 向内嵌 TapTap runtime 发送 `initialize` 与后续请求时,默认改为裸 JSON + 换行(NDJSON 风格),避免只发 `Content-Length` 帧导致无响应
|
|
147
|
+
- `initialize` 的 `protocolVersion` 回退到 `2024-11-05`,并使用更保守的空 `capabilities`,降低 OpenClaw 宿主兼容风险
|
|
94
148
|
- 兼容解析裸 JSON 输出,不再只接受 `Content-Length` 帧
|
|
95
149
|
- 启动时如果 stdout 混入人类可读日志,会先自动丢弃噪音再解析协议消息
|
|
96
150
|
- 如果拿到的是半截 JSON,会先继续等待后续分片,而不是立刻按失败处理
|
|
97
151
|
- 过滤 PTY 场景下被回显到 stdout 的请求消息
|
|
152
|
+
- 授权结果会优先把“直接点击授权”链接放在第一位,方便移动端对话直接打开
|
package/index.js
CHANGED
|
@@ -58,6 +58,55 @@ function buildMarkdownLink(label, url) {
|
|
|
58
58
|
return `[${label}](${url})`;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
function tryParseUrl(value) {
|
|
62
|
+
if (!value || typeof value !== 'string') {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
return new URL(value);
|
|
68
|
+
} catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function decodeWrappedAuthUrl(value) {
|
|
74
|
+
const parsed = tryParseUrl(value);
|
|
75
|
+
const wrappedCode = parsed?.searchParams?.get('code');
|
|
76
|
+
if (!wrappedCode) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
return decodeURIComponent(wrappedCode);
|
|
82
|
+
} catch {
|
|
83
|
+
return wrappedCode;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function enrichAuthPayload(authPayload) {
|
|
88
|
+
if (!authPayload || typeof authPayload !== 'object') {
|
|
89
|
+
return authPayload;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const directAuthUrl =
|
|
93
|
+
authPayload.direct_auth_url ||
|
|
94
|
+
authPayload.verification_uri_complete ||
|
|
95
|
+
authPayload.qrcode_url ||
|
|
96
|
+
decodeWrappedAuthUrl(authPayload.auth_url) ||
|
|
97
|
+
authPayload.verification_uri ||
|
|
98
|
+
null;
|
|
99
|
+
|
|
100
|
+
const wrappedAuthUrl = authPayload.wrapped_auth_url || authPayload.auth_url || null;
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
...authPayload,
|
|
104
|
+
direct_auth_url: directAuthUrl,
|
|
105
|
+
wrapped_auth_url: wrappedAuthUrl,
|
|
106
|
+
preferred_auth_url: directAuthUrl || wrappedAuthUrl || null,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
61
110
|
function normalizeText(value) {
|
|
62
111
|
return String(value || '')
|
|
63
112
|
.trim()
|
|
@@ -195,30 +244,39 @@ function buildBriefText(appTitle, sections, meta = {}) {
|
|
|
195
244
|
}
|
|
196
245
|
|
|
197
246
|
function buildAuthGuideText(authPayload) {
|
|
198
|
-
const
|
|
199
|
-
const
|
|
247
|
+
const enriched = enrichAuthPayload(authPayload);
|
|
248
|
+
const directAuthUrl = enriched?.direct_auth_url;
|
|
249
|
+
const wrappedAuthUrl = enriched?.wrapped_auth_url;
|
|
250
|
+
const qrcodeUrl = enriched?.qrcode_url;
|
|
200
251
|
const lines = [
|
|
201
|
-
'当前还没有完成 TapTap
|
|
252
|
+
'当前还没有完成 TapTap 授权,请先完成一次授权。',
|
|
253
|
+
'',
|
|
254
|
+
'如果你现在是在手机上或当前客户端支持超链接,请优先点击下面这条直接授权链接,不用扫码:',
|
|
255
|
+
buildMarkdownLink('直接点击授权', directAuthUrl),
|
|
202
256
|
'',
|
|
203
|
-
'
|
|
204
|
-
buildMarkdownLink('
|
|
257
|
+
'如果上面的链接打不开,再尝试这个 TapTap 包装授权页:',
|
|
258
|
+
buildMarkdownLink('打开 TapTap 包装授权页', wrappedAuthUrl),
|
|
205
259
|
'',
|
|
206
|
-
'
|
|
207
|
-
buildMarkdownLink('
|
|
260
|
+
'如果你需要在桌面端继续,或者想把授权页转给另一台设备,也可以使用这个直链自行打开或生成二维码:',
|
|
261
|
+
buildMarkdownLink('打开授权页直链', qrcodeUrl || directAuthUrl),
|
|
208
262
|
'',
|
|
209
263
|
'完成扫码后,请继续调用 `taptap_dc_complete_authorization`,然后再次调用 `taptap_dc_quick_brief`。',
|
|
210
264
|
];
|
|
211
265
|
|
|
212
|
-
if (
|
|
213
|
-
lines.push('', `device_code:${
|
|
266
|
+
if (enriched?.device_code) {
|
|
267
|
+
lines.push('', `device_code:${enriched.device_code}`);
|
|
214
268
|
}
|
|
215
269
|
|
|
216
|
-
if (
|
|
217
|
-
lines.push('',
|
|
270
|
+
if (directAuthUrl) {
|
|
271
|
+
lines.push('', `直接授权链接:${directAuthUrl}`);
|
|
218
272
|
}
|
|
219
273
|
|
|
220
|
-
if (
|
|
221
|
-
lines.push(
|
|
274
|
+
if (wrappedAuthUrl && wrappedAuthUrl !== directAuthUrl) {
|
|
275
|
+
lines.push(`TapTap 包装授权链接:${wrappedAuthUrl}`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (qrcodeUrl && qrcodeUrl !== directAuthUrl) {
|
|
279
|
+
lines.push(`授权页直链:${qrcodeUrl}`);
|
|
222
280
|
}
|
|
223
281
|
|
|
224
282
|
return lines.join('\n');
|
|
@@ -298,12 +356,15 @@ function registerProxyTool(api, bridge, definition) {
|
|
|
298
356
|
const text = await bridge.callTool(definition.mcpToolName, params || {});
|
|
299
357
|
const normalized = normalizeJsonText(text);
|
|
300
358
|
const parsed = tryParseJson(normalized);
|
|
359
|
+
const normalizedParsed = definition.normalizeResult
|
|
360
|
+
? definition.normalizeResult(parsed)
|
|
361
|
+
: parsed;
|
|
301
362
|
const renderedText = definition.resultFormatter
|
|
302
|
-
? definition.resultFormatter(
|
|
363
|
+
? definition.resultFormatter(normalizedParsed, normalized)
|
|
303
364
|
: normalized;
|
|
304
365
|
return toolResult(renderedText, {
|
|
305
366
|
mcpToolName: definition.mcpToolName,
|
|
306
|
-
parsed,
|
|
367
|
+
parsed: normalizedParsed,
|
|
307
368
|
});
|
|
308
369
|
} catch (error) {
|
|
309
370
|
return toolResult(
|
|
@@ -357,7 +418,9 @@ function registerQuickBriefTool(api, bridge) {
|
|
|
357
418
|
try {
|
|
358
419
|
const env = await callJsonTool(bridge, 'check_environment_raw', {});
|
|
359
420
|
if (!env?.auth?.has_mac_token) {
|
|
360
|
-
const authPayload =
|
|
421
|
+
const authPayload = enrichAuthPayload(
|
|
422
|
+
await callJsonTool(bridge, 'start_oauth_authorization_raw', {})
|
|
423
|
+
);
|
|
361
424
|
return toolResult(buildAuthGuideText(authPayload), authPayload);
|
|
362
425
|
}
|
|
363
426
|
|
|
@@ -425,6 +488,9 @@ const toolDefinitions = [
|
|
|
425
488
|
'Start TapTap OAuth device flow and return a mobile-friendly clickable auth link plus qrcode link.',
|
|
426
489
|
mcpToolName: 'start_oauth_authorization_raw',
|
|
427
490
|
parameters: createSchema(),
|
|
491
|
+
normalizeResult(parsed) {
|
|
492
|
+
return enrichAuthPayload(parsed);
|
|
493
|
+
},
|
|
428
494
|
resultFormatter(parsed, normalized) {
|
|
429
495
|
if (!parsed || typeof parsed !== 'object') {
|
|
430
496
|
return normalized;
|
package/lib/mcp-bridge.js
CHANGED
|
@@ -10,7 +10,10 @@ const require = createRequire(import.meta.url);
|
|
|
10
10
|
const HEADER_SEPARATOR = '\r\n\r\n';
|
|
11
11
|
const RUNTIME_PACKAGE_NAME = '@mikoto_zero/minigame-open-mcp';
|
|
12
12
|
const DEFAULT_INIT_TIMEOUT_MS = 45000;
|
|
13
|
+
const DEFAULT_PROTOCOL_VERSION = '2024-11-05';
|
|
13
14
|
const ANSI_ESCAPE_REGEX = /\x1B\[[0-?]*[ -/]*[@-~]/g;
|
|
15
|
+
const pluginPackageJson = readPluginPackageJson();
|
|
16
|
+
const PLUGIN_VERSION = pluginPackageJson?.version || '0.1.0';
|
|
14
17
|
|
|
15
18
|
function readPluginPackageJson() {
|
|
16
19
|
try {
|
|
@@ -563,8 +566,7 @@ export class TapTapMcpBridge {
|
|
|
563
566
|
}
|
|
564
567
|
|
|
565
568
|
const body = JSON.stringify(message);
|
|
566
|
-
|
|
567
|
-
this.child.stdin.write(frame, 'utf8');
|
|
569
|
+
this.child.stdin.write(`${body}\n`, 'utf8');
|
|
568
570
|
}
|
|
569
571
|
|
|
570
572
|
request(method, params) {
|
|
@@ -694,15 +696,11 @@ export class TapTapMcpBridge {
|
|
|
694
696
|
async initializeServer() {
|
|
695
697
|
await Promise.race([
|
|
696
698
|
this.request('initialize', {
|
|
697
|
-
protocolVersion:
|
|
698
|
-
capabilities: {
|
|
699
|
-
tools: {},
|
|
700
|
-
resources: {},
|
|
701
|
-
logging: {},
|
|
702
|
-
},
|
|
699
|
+
protocolVersion: DEFAULT_PROTOCOL_VERSION,
|
|
700
|
+
capabilities: {},
|
|
703
701
|
clientInfo: {
|
|
704
702
|
name: 'taptap-openclaw-dc-plugin',
|
|
705
|
-
version:
|
|
703
|
+
version: PLUGIN_VERSION,
|
|
706
704
|
},
|
|
707
705
|
}),
|
|
708
706
|
new Promise((_, reject) => {
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lotaber_wang/openclaw-dc-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "TapTap DC 的 OpenClaw 插件,内置原始数据工具与运营简报 skill。",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"openclaw": {
|
|
8
8
|
"extensions": [
|
|
9
9
|
"./index.js"
|
|
10
|
-
]
|
|
10
|
+
],
|
|
11
|
+
"install": {
|
|
12
|
+
"npmSpec": "@lotaber_wang/openclaw-dc-plugin"
|
|
13
|
+
}
|
|
11
14
|
},
|
|
12
15
|
"exports": {
|
|
13
16
|
".": "./index.js",
|
|
@@ -19,9 +19,9 @@ description: 生成 TapTap 当前游戏 DC 运营简报与结论解读(商店/
|
|
|
19
19
|
- 如果用户给了游戏名,直接把 `app_name` 传进去
|
|
20
20
|
- 如果用户给了 `app_id`,直接把 `app_id` 传进去
|
|
21
21
|
2. 如果未授权
|
|
22
|
-
- `taptap_dc_quick_brief` 会直接返回 markdown
|
|
23
|
-
-
|
|
24
|
-
-
|
|
22
|
+
- `taptap_dc_quick_brief` 会直接返回 markdown 授权链接,其中第一条通常就是可直接点击完成授权的链接
|
|
23
|
+
- 如果用户当前在手机上对话,优先引导用户直接点击第一条“直接点击授权”链接,不要先强调扫码
|
|
24
|
+
- 如果用户当前在桌面端对话,再引导用户打开授权页直链并扫码或转发到手机
|
|
25
25
|
- 用户确认后调用 `taptap_dc_complete_authorization`
|
|
26
26
|
- 然后再次调用 `taptap_dc_quick_brief`
|
|
27
27
|
3. 只有在用户明确要求更细的内容时,才退回到底层工具链
|
|
@@ -45,7 +45,7 @@ description: 生成 TapTap 当前游戏 DC 运营简报与结论解读(商店/
|
|
|
45
45
|
## 关键规则
|
|
46
46
|
|
|
47
47
|
- 这些 plugin tools 返回的是 **raw JSON**,你要自己完成解读,不要把 JSON 原样长篇贴回给用户
|
|
48
|
-
- 当授权工具已经返回可点击 markdown
|
|
48
|
+
- 当授权工具已经返回可点击 markdown 链接时,优先直接复用第一条直链,不要再把它改写成“去扫二维码”
|
|
49
49
|
- `page_view_count` 应写成“详情页访问量(PV)”,不要偷换成别的口径
|
|
50
50
|
- `taptap_dc_like_review` / `taptap_dc_reply_review` 只能在用户明确确认后调用
|
|
51
51
|
- 如果回复结果里出现 `need_confirmation=true`,必须先把草稿给用户确认,再决定是否带 `confirm_high_risk=true` 重试
|