@opentiny/webmcp-cli 0.0.3 → 0.0.4

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.
@@ -2,6 +2,12 @@
2
2
  "use strict";
3
3
  (() => {
4
4
  // webmcp-tools/juejin.cn/index.ts
5
+ function isJuejinDraftEditorPage() {
6
+ if (!/^https:\/\/juejin\.cn\/editor\/drafts\//.test(location.href)) {
7
+ return false;
8
+ }
9
+ return !!document.querySelector(".edit-draft .header .title-input");
10
+ }
5
11
  var mcp = navigator.modelContext;
6
12
  if (!mcp || typeof mcp.registerTool !== "function") {
7
13
  console.warn("[webmcp-tools] juejin.cn: navigator.modelContext.registerTool \u672A\u5C31\u7EEA\uFF0C\u8DF3\u8FC7\u6CE8\u5165");
@@ -37,8 +43,10 @@
37
43
  if (!content?.trim()) {
38
44
  throw new Error("\u53C2\u6570 content \u4E0D\u80FD\u4E3A\u7A7A");
39
45
  }
40
- if (!location.href.startsWith("https://juejin.cn/editor/drafts/new")) {
41
- throw new Error("\u5F53\u524D\u9875\u9762\u4E0D\u662F\u6398\u91D1\u65B0\u5EFA\u6587\u7AE0\u7F16\u8F91\u5668\uFF0C\u8BF7\u5148\u6253\u5F00 https://juejin.cn/editor/drafts/new?v=2");
46
+ if (!isJuejinDraftEditorPage()) {
47
+ throw new Error(
48
+ "\u5F53\u524D\u9875\u9762\u4E0D\u662F\u6398\u91D1\u6587\u7AE0\u7F16\u8F91\u5668\uFF08\u65B0\u5EFA\u6216\u8349\u7A3F\u9875\u5747\u53EF\uFF09\uFF0C\u8BF7\u5148\u6253\u5F00 https://juejin.cn/editor/drafts/new?v=2"
49
+ );
42
50
  }
43
51
  let decodeContent;
44
52
  try {
@@ -88,11 +96,16 @@
88
96
  throw new Error("\u672A\u627E\u5230\u6B63\u6587\u7F16\u8F91\u5668\uFF08CodeMirror\uFF09\uFF0C\u8BF7\u786E\u8BA4\u7F16\u8F91\u5668\u9875\u9762\u5DF2\u5B8C\u5168\u52A0\u8F7D");
89
97
  }
90
98
  return {
91
- success: true,
92
- message: "\u6587\u7AE0\u6807\u9898\u548C\u6B63\u6587\u5DF2\u6210\u529F\u586B\u5199\u5230\u6398\u91D1\u7F16\u8F91\u5668\uFF0C\u8349\u7A3F\u5C06\u81EA\u52A8\u4FDD\u5B58",
93
- title: title.trim(),
94
- contentLength: decodeContent.length,
95
- editor
99
+ content: [{
100
+ type: "text",
101
+ text: JSON.stringify({
102
+ success: true,
103
+ message: "\u6587\u7AE0\u6807\u9898\u548C\u6B63\u6587\u5DF2\u6210\u529F\u586B\u5199\u5230\u6398\u91D1\u7F16\u8F91\u5668\uFF0C\u8349\u7A3F\u5C06\u81EA\u52A8\u4FDD\u5B58",
104
+ title: title.trim(),
105
+ contentLength: decodeContent.length,
106
+ editor
107
+ })
108
+ }]
96
109
  };
97
110
  }
98
111
  });
@@ -118,9 +131,15 @@
118
131
  }
119
132
  }
120
133
  return {
121
- success: true,
122
- title,
123
- content
134
+ content: [{
135
+ type: "text",
136
+ text: JSON.stringify({
137
+ success: true,
138
+ title: title.trim(),
139
+ contentLength: content.length,
140
+ content
141
+ })
142
+ }]
124
143
  };
125
144
  }
126
145
  });
@@ -147,96 +166,146 @@
147
166
  required: ["summary"]
148
167
  },
149
168
  execute: async ({ category = "\u524D\u7AEF", tag = "Vue.js", summary }) => {
150
- const publishBtn = Array.from(document.querySelectorAll("button")).find(
169
+ if (!isJuejinDraftEditorPage()) {
170
+ throw new Error("\u672A\u5904\u4E8E\u6398\u91D1\u6587\u7AE0\u7F16\u8F91\u5668\u9875\u9762\uFF0C\u8BF7\u5148 tabs switch \u5230\u7F16\u8F91\u5668\u6807\u7B7E\u9875\u540E\u518D\u53D1\u5E03");
171
+ }
172
+ const editorRoot = document.querySelector(".edit-draft") ?? document;
173
+ const publishBtn = Array.from(editorRoot.querySelectorAll("button")).find(
151
174
  (e) => e.textContent?.trim() === "\u53D1\u5E03"
152
175
  );
153
176
  if (!publishBtn) {
154
- throw new Error("\u672A\u627E\u5230\u201C\u53D1\u5E03\u201D\u6309\u94AE\uFF0C\u8BF7\u786E\u8BA4\u5DF2\u5904\u4E8E\u6587\u7AE0\u7F16\u8F91\u5668\u9875\u9762");
177
+ throw new Error("\u672A\u627E\u5230\u201C\u53D1\u5E03\u201D\u6309\u94AE\uFF0C\u8BF7\u786E\u8BA4\u5DF2\u5904\u4E8E\u6587\u7AE0\u7F16\u8F91\u5668\u9875\u9762\u4E14\u9875\u9762\u5DF2\u5B8C\u5168\u52A0\u8F7D");
155
178
  }
156
179
  publishBtn.click();
157
180
  await new Promise((resolve) => setTimeout(resolve, 1e3));
158
- const categoryButtons = Array.from(
159
- document.querySelectorAll(".category-list .item, .category-list .category-item, .category-item, .item")
160
- );
161
- let catBtn = categoryButtons.find((e) => e.textContent?.includes(category));
162
- if (!catBtn) {
163
- catBtn = Array.from(document.querySelectorAll("*")).find(
164
- (e) => e.textContent === category && e.tagName !== "SCRIPT"
181
+ const dialogScope = document.querySelector('.panel, .modal, [role="dialog"]') || document;
182
+ const candidateSelectors = [
183
+ ".category-list .item",
184
+ // 掘金:直接命中
185
+ ".category-list li",
186
+ ".category-list button",
187
+ '.category-list [role="button"]',
188
+ ".category-item",
189
+ '[class*="category"] li',
190
+ '[class*="category"] span'
191
+ ];
192
+ let catBtn;
193
+ for (const sel of candidateSelectors) {
194
+ catBtn = Array.from(dialogScope.querySelectorAll(sel)).find(
195
+ (e) => e.textContent?.trim() === category
165
196
  );
197
+ if (catBtn) break;
198
+ }
199
+ if (!catBtn) {
200
+ catBtn = Array.from(dialogScope.querySelectorAll("div, span, li, button")).find((e) => {
201
+ const el = e;
202
+ return el.textContent?.trim() === category && el.offsetParent !== null;
203
+ });
166
204
  }
167
205
  if (!catBtn) {
168
206
  throw new Error(`\u672A\u627E\u5230\u5206\u7C7B\u6309\u94AE: ${category}`);
169
207
  }
170
208
  ;
171
209
  catBtn.click();
172
- const tagLabel = Array.from(document.querySelectorAll("span, label, .label")).find(
173
- (e) => e.textContent?.includes("\u6DFB\u52A0\u6807\u7B7E")
174
- );
175
- if (!tagLabel) {
176
- throw new Error("\u672A\u627E\u5230\u201C\u6DFB\u52A0\u6807\u7B7E\u201D\u7684\u8868\u5355\u9879");
177
- }
178
- const parent = tagLabel.closest(".form-item, .margin-bottom, .entry-form-item");
179
- const input = parent ? parent.querySelector("input") : null;
210
+ const input = dialogScope.querySelector('.byte-select__input, input[placeholder*="\u641C\u7D22\u6DFB\u52A0\u6807\u7B7E"], input[placeholder*="\u6DFB\u52A0\u6807\u7B7E"], input[placeholder*="\u6807\u7B7E"]');
180
211
  if (!input) {
181
212
  throw new Error("\u672A\u627E\u5230\u6807\u7B7E\u8F93\u5165\u6846");
182
213
  }
183
214
  input.focus();
184
- input.value = tag;
185
- input.dispatchEvent(new Event("input", { bubbles: true }));
215
+ const nativeInputValueSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set;
216
+ if (nativeInputValueSetter) {
217
+ nativeInputValueSetter.call(input, tag);
218
+ } else {
219
+ input.value = tag;
220
+ }
221
+ input.dispatchEvent(new InputEvent("input", { bubbles: true, cancelable: true, data: tag }));
186
222
  input.dispatchEvent(new Event("change", { bubbles: true }));
187
- await new Promise((resolve) => setTimeout(resolve, 1500));
188
- const items = Array.from(
223
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
224
+ const tagDropdownItems = Array.from(
189
225
  document.querySelectorAll(
190
- ".byte-select-option, .select-option, .option, .item, .dropdown-item, .byte-select__option, .byte-select-option-wrapper"
226
+ ".byte-select-option, .byte-select__option, .byte-select-option-wrapper, .select-option, .dropdown-item, .item"
191
227
  )
192
228
  );
193
- const tagItem = items.find((e) => e.textContent?.trim() === tag);
229
+ let tagItem = tagDropdownItems.find((e) => e.textContent?.trim() === tag);
230
+ if (!tagItem) {
231
+ tagItem = Array.from(document.querySelectorAll('li, [role="option"]')).find((e) => {
232
+ const el = e;
233
+ return el.textContent?.trim() === tag && el.offsetParent !== null;
234
+ });
235
+ }
194
236
  if (!tagItem) {
195
- throw new Error(`\u6807\u7B7E\u4E0B\u62C9\u9009\u9879\u4E2D\u672A\u627E\u5230\u5339\u914D\u7684: ${tag}`);
237
+ const foundTexts = tagDropdownItems.map((e) => e.textContent?.trim()).join(", ");
238
+ throw new Error(`\u6807\u7B7E\u4E0B\u62C9\u9009\u9879\u4E2D\u672A\u627E\u5230\u5339\u914D\u7684: ${tag}\u3002\u5F53\u524D\u627E\u5230\u7684\u9009\u9879\u6709: [${foundTexts}]\uFF0C\u8BF7\u68C0\u67E5\u6807\u7B7E\u540D\u79F0\u662F\u5426\u6B63\u786E\uFF0C\u6216\u624B\u52A8\u6253\u5F00\u4E0B\u62C9\u6846\u9009\u62E9`);
196
239
  }
197
240
  tagItem.click();
198
241
  let finalSummary = summary || "";
199
- if (finalSummary.length < 50) {
200
- while (finalSummary.length < 50) {
201
- finalSummary += "\u3002\u8FD9\u662F\u5173\u4E8E\u672C\u6587\u5185\u5BB9\u7684\u8BE6\u7EC6\u4ECB\u7ECD\uFF0C\u6B22\u8FCE\u9605\u8BFB\u5168\u6587\u4E86\u89E3\u66F4\u591A\u7CBE\u5F69\u7684\u6280\u672F\u7EC6\u8282\u4E0E\u5B9E\u8DF5\u7ECF\u9A8C\u5206\u4EAB\u3002";
202
- }
242
+ if (finalSummary.length < 50 || finalSummary.length > 100) {
243
+ throw new Error(`\u6458\u8981\u5B57\u6570\u5FC5\u987B\u5728 50 \u5230 100 \u5B57\u4E4B\u95F4\uFF0C\u5F53\u524D\u5B57\u6570\u4E3A\uFF1A${finalSummary.length}\u3002\u8BF7\u91CD\u65B0\u751F\u6210\u6458\u8981\u540E\u518D\u8BD5\u3002`);
203
244
  }
204
- if (finalSummary.length > 100) {
205
- finalSummary = finalSummary.slice(0, 97) + "...";
245
+ const summaryTextarea = dialogScope.querySelector(".byte-input__textarea") || dialogScope.querySelector("textarea") || dialogScope.querySelector('.summary-textarea, textarea[placeholder*="\u6458\u8981"]');
246
+ if (!summaryTextarea) {
247
+ throw new Error("\u672A\u627E\u5230\u6458\u8981\u8F93\u5165\u6846\uFF08.byte-input__textarea\uFF09\uFF0C\u8BF7\u68C0\u67E5\u5F39\u7A97\u662F\u5426\u6B63\u5E38\u6253\u5F00");
206
248
  }
207
- const summaryTextarea = document.querySelector('.summary-textarea, textarea[placeholder*="\u6458\u8981"], textarea[placeholder*="\u6587\u7AE0\u7684\u6458\u8981"]');
208
- if (summaryTextarea) {
209
- summaryTextarea.focus();
249
+ const nativeTextareaValueSetter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "value")?.set;
250
+ summaryTextarea.focus();
251
+ if (nativeTextareaValueSetter) {
252
+ nativeTextareaValueSetter.call(summaryTextarea, finalSummary);
253
+ } else {
210
254
  summaryTextarea.value = finalSummary;
211
- summaryTextarea.dispatchEvent(new Event("input", { bubbles: true }));
212
- summaryTextarea.dispatchEvent(new Event("change", { bubbles: true }));
213
- summaryTextarea.blur();
214
- await new Promise((resolve) => setTimeout(resolve, 500));
215
- }
216
- const titleClose = Array.from(document.querySelectorAll("*")).find(
217
- (e) => e.textContent?.trim() === "\u53D1\u5E03\u6587\u7AE0"
218
- );
219
- if (titleClose) {
220
- titleClose.click();
221
255
  }
256
+ summaryTextarea.dispatchEvent(new InputEvent("input", { bubbles: true, cancelable: true }));
257
+ summaryTextarea.dispatchEvent(new Event("change", { bubbles: true }));
258
+ summaryTextarea.blur();
259
+ await new Promise((resolve) => setTimeout(resolve, 300));
260
+ input.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", bubbles: true }));
261
+ input.blur();
222
262
  await new Promise((resolve) => setTimeout(resolve, 500));
223
- const confirmBtn = Array.from(document.querySelectorAll("button")).find(
263
+ let confirmBtn = Array.from(dialogScope.querySelectorAll("button")).find(
224
264
  (e) => e.textContent?.trim() === "\u786E\u5B9A\u5E76\u53D1\u5E03"
225
265
  );
226
266
  if (!confirmBtn) {
227
267
  throw new Error("\u672A\u627E\u5230\u201C\u786E\u5B9A\u5E76\u53D1\u5E03\u201D\u6309\u94AE");
228
268
  }
229
269
  confirmBtn.click();
230
- await new Promise((resolve) => setTimeout(resolve, 500));
231
- const popupTitle = Array.from(document.querySelectorAll("*")).find(
232
- (e) => e.textContent?.trim() === "\u53D1\u5E03\u6587\u7AE0" && e.offsetParent !== null
233
- );
234
- if (popupTitle) {
235
- throw new Error("\u53D1\u5E03\u6587\u7AE0\u5931\u8D25\uFF0C\u5F39\u51FA\u6846\u672A\u5173\u95ED\uFF0C\u8BF7\u68C0\u67E5\u5206\u7C7B\u3001\u6807\u7B7E\u6216\u5176\u4ED6\u5FC5\u586B\u9879\u662F\u5426\u6B63\u786E\u586B\u5199");
270
+ const publishResult = await Promise.race([
271
+ // 信号1:轮询检测弹窗是否消失
272
+ new Promise((resolve) => {
273
+ const check = setInterval(() => {
274
+ const panel = document.querySelector(".panel");
275
+ if (!panel || panel.offsetParent === null) {
276
+ clearInterval(check);
277
+ resolve("popup_closed");
278
+ }
279
+ }, 200);
280
+ setTimeout(() => {
281
+ clearInterval(check);
282
+ }, 5e3);
283
+ }),
284
+ // 信号2:监听页面跳转(beforeunload / hashchange 均可触发)
285
+ new Promise((resolve) => {
286
+ const handler = () => resolve("navigating");
287
+ window.addEventListener("beforeunload", handler, { once: true });
288
+ setTimeout(() => {
289
+ window.removeEventListener("beforeunload", handler);
290
+ }, 5e3);
291
+ }),
292
+ // 信号3:超时兜底
293
+ new Promise((resolve) => setTimeout(() => resolve("timeout"), 5e3))
294
+ ]);
295
+ if (publishResult === "timeout") {
296
+ const panel = document.querySelector(".panel");
297
+ if (panel && panel.offsetParent !== null) {
298
+ throw new Error("\u53D1\u5E03\u6587\u7AE0\u5931\u8D25\uFF0C\u5F39\u51FA\u6846\u672A\u5173\u95ED\uFF0C\u8BF7\u68C0\u67E5\u5206\u7C7B\u3001\u6807\u7B7E\u6216\u5176\u4ED6\u5FC5\u586B\u9879\u662F\u5426\u6B63\u786E\u586B\u5199");
299
+ }
236
300
  }
237
301
  return {
238
- success: true,
239
- message: `\u81EA\u52A8\u53D1\u5E03\u6D41\u7A0B\u5DF2\u89E6\u53D1\uFF0C\u5206\u7C7B: ${category}, \u6807\u7B7E: ${tag}`
302
+ content: [{
303
+ type: "text",
304
+ text: JSON.stringify({
305
+ success: true,
306
+ message: `\u81EA\u52A8\u53D1\u5E03\u6D41\u7A0B\u5DF2\u89E6\u53D1\uFF0C\u5206\u7C7B: ${category}, \u6807\u7B7E: ${tag}\uFF0C\u53D1\u5E03\u4FE1\u53F7: ${publishResult}`
307
+ })
308
+ }]
240
309
  };
241
310
  }
242
311
  });
@@ -4,8 +4,10 @@
4
4
  // webmcp-tools/my.oschina.net/index.ts
5
5
  var mcp = navigator.modelContext;
6
6
  if (!mcp || typeof mcp.registerTool !== "function") {
7
- console.warn("[webmcp-tools] juejin.cn: navigator.modelContext.registerTool \u672A\u5C31\u7EEA\uFF0C\u8DF3\u8FC7\u6CE8\u5165");
8
- } else if (!window.__webmcptools_juejincn) {
7
+ console.warn("[webmcp-tools] my.oschina.net: navigator.modelContext.registerTool \u672A\u5C31\u7EEA\uFF0C\u8DF3\u8FC7\u6CE8\u5165");
8
+ } else if (!window.__webmcptools_myoschinanet) {
9
+ ;
10
+ window.__webmcptools_myoschinanet = true;
9
11
  try {
10
12
  mcp.registerTool({
11
13
  name: "create_article",
@@ -5,7 +5,9 @@
5
5
  var _baiduMcp = navigator.modelContext;
6
6
  if (!_baiduMcp || typeof _baiduMcp.registerTool !== "function") {
7
7
  console.warn("[webmcp-tools] www.baidu.com: navigator.modelContext.registerTool \u672A\u5C31\u7EEA\uFF0C\u8DF3\u8FC7\u6CE8\u5165");
8
- } else if (!window.__webmcptools_baidu) {
8
+ } else if (!window.__webmcptools_wwwbaiducom) {
9
+ ;
10
+ window.__webmcptools_wwwbaiducom = true;
9
11
  try {
10
12
  _baiduMcp.registerTool({
11
13
  name: "baidu_search",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opentiny/webmcp-cli",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "type": "module",
5
5
  "description": "WebMCP CLI for AI Agents to interact with browser via Puppeteer",
6
6
  "main": "dist/index.js",