writedock-mcp 0.4.7 → 0.4.9
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 +7 -3
- package/extension/assets/editor-DSj8NlXD.js +76 -0
- package/extension/assets/index.ts-C3FUEli7.js +42 -0
- package/extension/manifest.json +1 -1
- package/extension/service-worker-loader.js +1 -1
- package/extension/src/editor/index.html +1 -1
- package/extension//345/256/211/350/243/205/344/270/216/346/233/264/346/226/260.txt +4 -3
- package/index.cjs +20 -17
- package/package.json +1 -1
- package/source/package.json +1 -1
- package/source/src/__tests__/setup.test.ts +19 -0
- package/source/src/bridge.ts +1 -1
- package/source/src/server.ts +3 -3
- package/source/src/setup.ts +22 -12
- package/source/tsup.config.ts +2 -0
- package/extension/assets/editor-ClIT9WYa.js +0 -76
- package/extension/assets/index.ts-BEVXnN62.js +0 -42
package/extension/manifest.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import './assets/index.ts-
|
|
1
|
+
import './assets/index.ts-C3FUEli7.js';
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<meta name="color-scheme" content="light" />
|
|
7
7
|
<title>写作台 · WriteDock</title>
|
|
8
8
|
<link rel="icon" type="image/png" href="/assets/icon-48.png" />
|
|
9
|
-
<script type="module" crossorigin src="/assets/editor-
|
|
9
|
+
<script type="module" crossorigin src="/assets/editor-DSj8NlXD.js"></script>
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/purify.es-1yEQDPgc.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/react-vendor-BrXlnJmF.js">
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/assets/task-utils-DwzGcJkm.js">
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
WriteDock 安装与更新 v0.4.
|
|
1
|
+
WriteDock 安装与更新 v0.4.9
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
1. 把这句话发给你的 agent:帮我安装 WriteDock
|
|
3
|
+
首次使用
|
|
4
|
+
1. 把这句话发给你的 agent:帮我安装 WriteDock:先检查电脑上有没有 Node.js 22 或更高版本,没有就帮我安装,需要我操作时告诉我;然后运行 npx -y writedock-mcp@latest setup,最后告诉我怎么重启你。
|
|
5
|
+
如果 agent 装不上 Node.js:到 nodejs.org 下载 LTS 版,一路点下一步,重启 agent 后再发一次这句话。
|
|
5
6
|
2. 按提示重启 agent,并在 Chrome / Edge 扩展管理页打开开发者模式,加载提示中的文件夹。这一步只需做一次。
|
|
6
7
|
3. 打开扩展,点“连接”。
|
|
7
8
|
4. 在同一个浏览器里登录要发布的平台。
|
package/index.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
if (Number(process.versions.node.split('.')[0]) < 22) { console.error('WriteDock 需要 Node.js 22 或更高版本,当前是 ' + process.version + '。请安装 https://nodejs.org 的 LTS 版,然后重启 agent。'); process.exit(1) }
|
|
2
3
|
"use strict";
|
|
3
4
|
var __create = Object.create;
|
|
4
5
|
var __defProp = Object.defineProperty;
|
|
@@ -29739,7 +29740,7 @@ var MediaStore = class {
|
|
|
29739
29740
|
// package.json
|
|
29740
29741
|
var package_default = {
|
|
29741
29742
|
name: "@writedock/mcp-server",
|
|
29742
|
-
version: "0.4.
|
|
29743
|
+
version: "0.4.9",
|
|
29743
29744
|
private: true,
|
|
29744
29745
|
type: "module",
|
|
29745
29746
|
description: "WriteDock local MCP server for the browser extension",
|
|
@@ -30205,7 +30206,7 @@ var LocalBridge = class {
|
|
|
30205
30206
|
return await this.httpCall("/assets/register", { path, kind });
|
|
30206
30207
|
}
|
|
30207
30208
|
async requestConnected(method, params) {
|
|
30208
|
-
if (this.client?.readyState !== import_websocket.default.OPEN) throw new Error("\u6269\u5C55\u672A\u8FDE\u63A5\u3002\u8BF7\u8BA9\u7528\u6237\u6253\u5F00\
|
|
30209
|
+
if (this.client?.readyState !== import_websocket.default.OPEN) throw new Error("\u6269\u5C55\u672A\u8FDE\u63A5\uFF0C\u8FD9\u6B21\u6CA1\u6709\u53D1\u5E03\u3002\u8BF7\u8BA9\u7528\u6237\u6253\u5F00 Chrome \u6216 Edge\uFF0C\u8FDE\u63A5\u8FC7\u7684\u6269\u5C55\u901A\u5E38\u4F1A\u81EA\u52A8\u8FDE\u4E0A\uFF0C\u7136\u540E\u518D\u8BD5\uFF1B\u5982\u679C\u8FD8\u662F\u4E0D\u884C\uFF0C\u6253\u5F00 WriteDock \u6269\u5C55\u70B9\u201C\u8FDE\u63A5\u201D\u3002\u65E0\u9700\u91CD\u88C5 MCP\u3002");
|
|
30209
30210
|
const id = (0, import_node_crypto2.randomUUID)();
|
|
30210
30211
|
return new Promise((resolve4, reject) => {
|
|
30211
30212
|
const timer = setTimeout(() => {
|
|
@@ -50328,7 +50329,7 @@ var postSource = {
|
|
|
50328
50329
|
weibo_video_creation: external_exports.enum(["original", "secondary", "repost"]).optional().describe("\u5FAE\u535A\u72EC\u7ACB\u89C6\u9891\u5FC5\u586B\uFF1Aoriginal \u539F\u521B\u3001secondary \u4E8C\u521B\u3001repost \u8F6C\u8F7D\u3002\u5FC5\u987B\u6839\u636E\u7528\u6237\u5B9E\u9645\u5185\u5BB9\u660E\u786E\u9009\u62E9\uFF0C\u4E0D\u5F97\u9ED8\u8BA4\u539F\u521B\u3002\u5176\u4ED6\u5E73\u53F0\u4E0D\u4F7F\u7528\u6B64\u5B57\u6BB5\uFF1B\u6765\u6E90\u3001\u5185\u5BB9\u58F0\u660E\u6216\u534F\u8BAE\u9700\u8865\u5145\u65F6\u8F6C\u4EBA\u5DE5\u5904\u7406"),
|
|
50329
50330
|
base_directory: external_exports.string().optional().describe("\u4F7F\u7528\u76F8\u5BF9\u7D20\u6750\u8DEF\u5F84\u65F6\uFF0C\u660E\u786E\u6307\u5B9A\u7D20\u6750\u76EE\u5F55")
|
|
50330
50331
|
};
|
|
50331
|
-
var agentWorkflow = "Agent \u53EA\u901A\u8FC7 WriteDock MCP \u6295\u7A3F\u548C\u67E5\u8BE2\u56DE\u6267\uFF0C\u4E0D\u4F9D\u8D56\u4E5F\u4E0D\u8981\u8C03\u7528\u6D4F\u89C8\u5668\u3001Playwright \u6216\u5176\u4ED6\u7F51\u9875\u5DE5\u5177\u3002\u6309\u7ED3\u679C\u7684 next_action \u64CD\u4F5C\uFF1Apoll_task \u7528\u539F task_id/request_id \u67E5\u8BE2 get_sync_task\uFF1Breport_result \u76F4\u63A5\u5411\u7528\u6237\u6C47\u62A5\u9010\u5E73\u53F0\u72B6\u6001\u548C\u94FE\u63A5\u3002submitted/published \u5DF2\u6709\u6269\u5C55\u786E\u8BA4\uFF0C\u4E0D\u518D\u6253\u5F00\u7F51\u9875\u9A8C\u8BC1\uFF1B\u9A8C\u8BC1\u7801\u3001\u56FE\u5F62\u9A8C\u8BC1\u3001\u534F\u8BAE\u3001\u53D1\u5E03\u8BBE\u7F6E\u6216 unknown/interrupted \u90FD\u5C06\u539F\u56E0\u548C\u94FE\u63A5\u4EA4\u7ED9\u7528\u6237\u81EA\u884C\u70B9\u51FB\u5904\u7406\uFF0C\u4E0D\u81EA\u52A8\u91CD\u53D1\u3001\u4E0D\u6362 request_id\u3002\u4EBA\u5DE5\u63A5\u7BA1\u4E0D\u4EE3\u8868\u5DF2\u7ECF\u53D1\u5E03\u3002";
|
|
50332
|
+
var agentWorkflow = "Agent \u53EA\u901A\u8FC7 WriteDock MCP \u6295\u7A3F\u548C\u67E5\u8BE2\u56DE\u6267\uFF0C\u4E0D\u4F9D\u8D56\u4E5F\u4E0D\u8981\u8C03\u7528\u6D4F\u89C8\u5668\u3001Playwright \u6216\u5176\u4ED6\u7F51\u9875\u5DE5\u5177\u3002\u6309\u7ED3\u679C\u7684 next_action \u64CD\u4F5C\uFF1Apoll_task \u7528\u539F task_id/request_id \u67E5\u8BE2 get_sync_task\uFF1Breport_result \u76F4\u63A5\u5411\u7528\u6237\u6C47\u62A5\u9010\u5E73\u53F0\u72B6\u6001\u548C\u94FE\u63A5\u3002submitted/published \u5DF2\u6709\u6269\u5C55\u786E\u8BA4\uFF0C\u4E0D\u518D\u6253\u5F00\u7F51\u9875\u9A8C\u8BC1\uFF1B\u9A8C\u8BC1\u7801\u3001\u56FE\u5F62\u9A8C\u8BC1\u3001\u534F\u8BAE\u3001\u53D1\u5E03\u8BBE\u7F6E\u6216 unknown/interrupted \u90FD\u5C06\u539F\u56E0\u548C\u94FE\u63A5\u4EA4\u7ED9\u7528\u6237\u81EA\u884C\u70B9\u51FB\u5904\u7406\uFF0C\u4E0D\u81EA\u52A8\u91CD\u53D1\u3001\u4E0D\u6362 request_id\u3002\u4EBA\u5DE5\u63A5\u7BA1\u4E0D\u4EE3\u8868\u5DF2\u7ECF\u53D1\u5E03\u3002\u552F\u4E00\u4F8B\u5916\u662F user_actions \u7684 type=old_draft\uFF1A\u5E73\u53F0\u6709\u4E00\u4EFD\u7528\u6237\u4E0A\u6B21\u6CA1\u53D1\u5B8C\u7684\u5185\u5BB9\uFF0C\u8FD9\u7BC7\u5C1A\u672A\u63D0\u4EA4\u3002\u5FC5\u987B\u5148\u95EE\u7528\u6237\u662F\u5426\u653E\u5F03\u90A3\u4EFD\u65E7\u5185\u5BB9\uFF0C\u4E0D\u5F97\u66FF\u7528\u6237\u51B3\u5B9A\uFF1B\u7528\u6237\u660E\u786E\u540C\u610F\u540E\uFF0C\u624D\u7528\u65B0\u7684 request_id \u53EA\u5BF9\u8BE5\u5E73\u53F0\u518D\u8C03\u7528 publish\uFF0C\u5E76\u4F20 discard_old_drafts\uFF1B\u7528\u6237\u4E0D\u540C\u610F\u5C31\u4FDD\u7559\u65E7\u5185\u5BB9\uFF0C\u544A\u8BC9\u7528\u6237\u8FD9\u4E2A\u5E73\u53F0\u6CA1\u6709\u53D1\u3002";
|
|
50332
50333
|
function createMcpServer(bridge) {
|
|
50333
50334
|
const server = new McpServer({ name: "writedock", version: package_default.version }, {
|
|
50334
50335
|
instructions: agentWorkflow + 'WriteDock \u901A\u8FC7\u672C\u673A\u6269\u5C55\u6267\u884C\u6295\u7A3F\u3002\u5148\u68C0\u67E5\u8FDE\u63A5\u548C\u5E73\u53F0\u767B\u5F55\u3002\u5B9E\u9645\u5E73\u53F0\u80FD\u529B\u4EE5 list_platforms \u8FD4\u56DE\u7684 action\u3001publishModes\u3001tools\u3001modeOptions\u3001confirmation\u3001capabilities\u3001coverRequired \u4E3A\u51C6\uFF0C\u6269\u5C55\u66F4\u65B0\u540E\u4F18\u5148\u6309\u8BE5\u5143\u4FE1\u606F\u9009\u62E9\u5DE5\u5177\u3002\u77E5\u4E4E\u3001\u767E\u5BB6\u53F7\u3001\u5FAE\u535A\u957F\u6587\u7AE0\u3001B\u7AD9\u4E13\u680F\u3001\u5C0F\u7EA2\u4E66\u957F\u6587\u4F7F\u7528 publish_article\uFF0CAgent \u76F4\u63A5\u63D0\u4F9B Markdown/HTML \u6216\u6587\u4EF6\u8DEF\u5F84\u5373\u53EF\u53D1\u5E03\uFF0C\u65E0\u9700\u7528\u6237\u5728\u6269\u5C55\u7F16\u8F91\u3002\u767E\u5BB6\u53F7\u5FC5\u987B\u63D0\u4F9B cover \u5C01\u9762\uFF1B\u77E5\u4E4E\u53E6\u652F\u6301 <video src="\u672C\u5730MP4\u8DEF\u5F84"></video> \u6587\u7AE0\u5185\u89C6\u9891\uFF0C\u72EC\u7ACB\u5C01\u9762\u6682\u672A\u63A5\u5165\u3002prepare_article \u4EC5\u4F9B\u53EF\u9009\u672C\u673A\u9884\u89C8\uFF0C\u4E0D\u662F\u53D1\u5E03\u7684\u524D\u7F6E\u8981\u6C42\u3002\u5C0F\u7EA2\u4E66 ID \u4E3A xiaohongshu\uFF0C\u4E09\u79CD\u6A21\u5F0F\u5171\u7528\u6B64\u5E73\u53F0\uFF1A\u5199\u957F\u6587\u7528 publish_article\uFF0C\u652F\u6301\u6B63\u6587\u56FE\u7247\u3001\u539F\u751F\u81EA\u52A8\u6392\u7248\u540E\u53D1\u5E03\uFF0C\u6807\u9898\u6700\u591A 64 \u5B57\u3001\u6B63\u6587\u6700\u591A 10000 \u5B57\uFF0C\u4E0D\u63A5\u72EC\u7ACB cover \u6216\u6587\u7AE0\u5185\u89C6\u9891\uFF1B\u666E\u901A\u56FE\u6587\u7528 publish_post(kind=image)\uFF0C\u72EC\u7ACB\u89C6\u9891\u7528 publish_post(kind=video)\uFF0C\u672A\u4F20 cover_path \u65F6\u4FDD\u7559\u5E73\u53F0\u81EA\u52A8\u751F\u6210\u7684\u7B2C\u4E00\u5E27\u5C01\u9762\u3002\u5C0F\u7EA2\u4E66\u6700\u7EC8\u5747\u987B\u5339\u914D POST /web_api/sns/v2/note \u7684 success=true\u3001result=0 \u548C\u7B14\u8BB0 ID \u56DE\u6267\u624D\u786E\u8BA4 submitted\u3002\u6296\u97F3\u5E73\u53F0 ID \u4E3A douyin\uFF0C\u56FE\u6587\u548C\u89C6\u9891\u540C\u6837\u7528 publish_post(kind=image|video)\uFF0C\u53EF\u9009 cover_path\uFF1B\u56FE\u6587\u8BDD\u9898\u6700\u591A 4 \u4E2A\u3001\u89C6\u9891\u6700\u591A 5 \u4E2A\uFF0C\u5F53\u524D\u53EA\u63A5\u5373\u65F6\u53D1\u5E03\u3002\u6296\u97F3 confirmation=native_page_message\uFF0C\u4EE5\u672C\u6B21\u64CD\u4F5C\u65B0\u589E\u7684\u539F\u751F\u6210\u529F\u63D0\u793A\u786E\u8BA4 submitted\uFF0C\u672A\u6838\u5BF9\u6700\u7EC8 HTTP \u4E1A\u52A1\u56DE\u6267\uFF1B\u4EC5\u8DF3\u8F6C\u4F5C\u54C1\u7BA1\u7406\u65F6\u4ECD\u4E3A unknown\uFF0CAgent \u4E0D\u81EA\u52A8\u91CD\u53D1\u3002\u5C0F\u7EA2\u4E66\u3001\u6296\u97F3\u3001\u5FAE\u535A\u666E\u901A\u56FE\u6587\u4E0E\u72EC\u7ACB\u89C6\u9891\u4F7F\u7528 prepare_post / publish_post\uFF0C\u5FAE\u535A\u5E73\u53F0 ID \u4ECD\u4E3A weibo\uFF0C\u4E0D\u53E6\u5EFA\u5E73\u53F0\u3002\u5FAE\u535A\u56FE\u6587\u6807\u9898\u653E\u5165\u6B63\u6587\u9996\u884C\uFF0C\u6700\u591A 18 \u5F20\u56FE\u7247\uFF1B\u72EC\u7ACB\u89C6\u9891\u6807\u9898\u6700\u591A 30 \u4E2A\u6C49\u5B57\uFF0860 \u4E2A\u534A\u89D2\u5B57\u7B26\uFF09\uFF0Cweibo_video_creation \u5FC5\u586B\uFF0C\u987B\u4F9D\u636E\u7528\u6237\u5B9E\u9645\u5185\u5BB9\u4F20 original\u3001secondary \u6216 repost\uFF0C\u4E0D\u80FD\u9ED8\u8BA4\u539F\u521B\u3002\u5FAE\u535A\u540C\u4E00\u6B21\u53EA\u80FD\u9009\u56FE\u6587\u6216\u89C6\u9891\uFF0C\u957F\u6587\u7AE0\u7EE7\u7EED\u7528 publish_article\u3002\u53EA\u6709\u7528\u6237\u660E\u786E\u8981\u6C42\u53D1\u5E03\u5E76\u6307\u5B9A\u5E73\u53F0\u65F6\u624D\u8C03\u7528\u53D1\u5E03\u5DE5\u5177\u3002\u5FAE\u4FE1\u516C\u4F17\u53F7\u3001\u641C\u72D0\u53F7\u76EE\u524D\u7528 sync_article \u4FDD\u5B58\u6587\u7AE0\u8349\u7A3F\u3002B\u7AD9\u4E13\u680F ID \u4E3A bilibili\uFF0C\u4F7F\u7528 publish_article \u76F4\u63A5\u53D1\u5E03\u6587\u5B57\u548C\u6B63\u6587\u56FE\u7247\uFF0C\u4E0D\u63A5\u89C6\u9891\u3001\u97F3\u9891\u3001\u9644\u52A0\u5185\u5BB9\u5361\u7247\u6216\u72EC\u7ACB\u5C01\u9762\uFF1B\u901A\u8FC7\u65B0\u7248\u539F\u751F\u7F16\u8F91\u5668\u4E0A\u4F20\u5E76\u53D1\u5E03\uFF0Ccode=-352 \u65F6\u505C\u6B62\u81EA\u52A8\u63D0\u4EA4\u5E76\u8F6C\u4EBA\u5DE5\u9A8C\u8BC1\u3002\u9047\u5230 needs_user_action \u6216 requires_user_action \u65F6\uFF0C\u628A user_actions \u4E2D\u7684\u539F\u56E0\u548C\u7A3F\u4EF6\u94FE\u63A5\u544A\u8BC9\u7528\u6237\uFF0C\u4F18\u5148\u8BA9\u7528\u6237\u5207\u6362\u5230\u6269\u5C55\u5DF2\u6253\u5F00\u7684\u9875\u9762\u5904\u7406\u9A8C\u8BC1\u7801\u3001\u5C01\u9762\u3001\u5185\u5BB9\u58F0\u660E\u3001\u6765\u6E90\u3001\u539F\u521B\u534F\u8BAE\u6216\u53D1\u5E03\u8BBE\u7F6E\uFF0C\u5E76\u624B\u52A8\u53D1\u5E03\uFF1B\u4E0D\u5F97\u81EA\u52A8\u89E3\u51B3\u9A8C\u8BC1\u7801\u3001\u91CD\u65B0\u63D0\u4EA4\u6216\u6362 request_id\u3002\u8BE5\u72B6\u6001\u53EA\u8BB0\u5F55\u4EBA\u5DE5\u63A5\u7BA1\uFF0C\u4E0D\u4EE3\u8868\u7528\u6237\u4E4B\u540E\u7684\u64CD\u4F5C\u7ED3\u679C\u5DF2\u88AB\u786E\u8BA4\u3002\u6269\u5C55\u66F4\u65B0\u590D\u7528\u5DF2\u6709 MCP\uFF1B\u672A\u8FDE\u63A5\u65F6\uFF0C\u8BA9\u7528\u6237\u6253\u5F00 WriteDock \u6269\u5C55\u7684\u300C\u8FDE\u63A5\u300D\u9875\u70B9\u201C\u8FDE\u63A5\u201D\u3002\u63D0\u4EA4\u8FD4\u56DE\u4EFB\u52A1 ID\uFF0C\u968F\u540E\u7528 get_sync_task \u67E5\u8BE2\u7ED3\u679C\u3002\u4FDD\u6301\u540C\u4E00\u64CD\u4F5C\u7684 request_id \u4E0D\u53D8\u3002publicationState=unknown \u6216\u4EFB\u52A1\u4E2D\u65AD\u65F6\uFF0C\u5411\u7528\u6237\u7ED9\u51FA\u94FE\u63A5\u8BA9\u5176\u81EA\u884C\u6838\u5BF9\u5E73\u53F0\uFF0CAgent \u4E0D\u6253\u5F00\u7F51\u9875\uFF0C\u4E0D\u8981\u81EA\u52A8\u751F\u6210\u65B0 request_id \u91CD\u53D1\u3002\u76F4\u63A5\u53D1\u5E03\u6210\u529F\u8868\u793A\u5E73\u53F0\u5DF2\u63A5\u53D7\u63D0\u4EA4\uFF0C\u662F\u5426\u516C\u5F00\u5C55\u793A\u4EE5\u5E73\u53F0\u4E3A\u51C6\u3002'
|
|
@@ -50400,9 +50401,9 @@ function createMcpServer(bridge) {
|
|
|
50400
50401
|
};
|
|
50401
50402
|
server.registerTool("publish", {
|
|
50402
50403
|
description: agentWorkflow + "\u9996\u9009\u7684\u53D1\u5E03\u5DE5\u5177\uFF1A\u4E00\u6B21\u8C03\u7528\u53D1\u5E03\u5230\u7528\u6237\u6307\u5B9A\u7684\u591A\u4E2A\u5E73\u53F0\u3002\u53EA\u6709\u7528\u6237\u660E\u786E\u8981\u6C42\u53D1\u5E03\u5E76\u6307\u5B9A\u5E73\u53F0\u65F6\u624D\u8C03\u7528\u3002\u6309\u5E73\u53F0\u4F9D\u6B21\u6267\u884C\uFF0C\u67D0\u4E2A\u5E73\u53F0\u672A\u767B\u5F55\u3001\u4E0D\u80FD\u63A5\u6536\u5185\u5BB9\u6216\u5931\u8D25\uFF0C\u53EA\u8DF3\u8FC7\u8BE5\u5E73\u53F0\uFF0C\u5176\u4ED6\u5E73\u53F0\u7EE7\u7EED\u3002\u9047\u5230\u9A8C\u8BC1\u7801\u65F6\u5E73\u53F0\u8FDB\u5165 waiting_verification\uFF0C\u7ACB\u5373\u628A\u539F\u56E0\u548C\u94FE\u63A5\u544A\u8BC9\u7528\u6237\uFF0C\u7531\u7528\u6237\u624B\u52A8\u5B8C\u6210\uFF0C\u7EE7\u7EED\u67E5\u8BE2\u5373\u53EF\uFF0C\u4E0D\u8981\u91CD\u53D1\u3002\u4EFB\u52A1\u7ED3\u675F\u540E\u6309 get_sync_task \u7684 summary \u9010\u5E73\u53F0\u544A\u8BC9\u7528\u6237\uFF1A\u54EA\u4E9B\u6210\u529F\uFF0C\u54EA\u4E9B\u672A\u767B\u5F55\uFF08not_logged_in\uFF09\u7B49\u3002\u7ACB\u5373\u8FD4\u56DE task_id\uFF0C\u518D\u7528 get_sync_task \u67E5\u8BE2\uFF1Bresults \u662F\u9010\u5E73\u53F0\u7ED3\u679C\uFF0Cwarnings \u8BF4\u660E\u4E3A\u5E73\u53F0\u505A\u7684\u8C03\u6574\u3002\u5FC5\u987B\u4F7F\u7528\u7A33\u5B9A request_id\uFF0C\u76F8\u540C ID \u4E0D\u4F1A\u91CD\u590D\u63D0\u4EA4\u3002\u90E8\u5206\u5E73\u53F0\u5931\u8D25\u65F6\uFF0C\u53EA\u9488\u5BF9\u5931\u8D25\u5E73\u53F0\u7528\u65B0 request_id \u91CD\u65B0\u63D0\u4EA4\uFF0Cunknown \u7684\u5E73\u53F0\u4E0D\u8981\u91CD\u53D1\u3002\u65E7\u7248\u6269\u5C55\u4E0D\u652F\u6301\u65F6\u4F1A\u62A5\u9519\uFF0C\u4E0D\u4F1A\u53D1\u5E03\u3002",
|
|
50403
|
-
inputSchema: { ...unifiedInput, request_id: requestId },
|
|
50404
|
+
inputSchema: { ...unifiedInput, request_id: requestId, discard_old_drafts: external_exports.array(external_exports.enum(["douyin"])).optional().describe('\u53EA\u6709\u7528\u6237\u660E\u786E\u540C\u610F\u653E\u5F03\u5E73\u53F0\u4E0A\u6B21\u6CA1\u53D1\u5B8C\u7684\u5185\u5BB9\u65F6\u624D\u4F20\uFF0C\u5982 ["douyin"]\u3002\u653E\u5F03\u540E\u65E0\u6CD5\u6062\u590D\u3002') },
|
|
50404
50405
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
50405
|
-
}, (args) => wrap(async () => bridge.request("publish", { requestId: args.request_id, ...await unifiedParams(args) })));
|
|
50406
|
+
}, (args) => wrap(async () => bridge.request("publish", { requestId: args.request_id, ...args.discard_old_drafts ? { discardOldDrafts: args.discard_old_drafts } : {}, ...await unifiedParams(args) })));
|
|
50406
50407
|
server.registerTool("rehearse_publish", {
|
|
50407
50408
|
description: "\u4E0E publish \u53C2\u6570\u76F8\u540C\u7684\u6F14\u7EC3\uFF1A\u9010\u5E73\u53F0\u6253\u5F00\u539F\u751F\u9875\u9762\uFF0C\u586B\u597D\u5185\u5BB9\u3001\u4E0A\u4F20\u7D20\u6750\uFF0C\u8D70\u5230\u70B9\u51FB\u53D1\u5E03\u4E4B\u524D\u505C\u4E0B\uFF0C\u4E0D\u63D0\u4EA4\u53D1\u5E03\u3002\u65E0\u9700 request_id\uFF0C\u4E0D\u5F71\u54CD\u4E4B\u540E\u7684\u771F\u5B9E\u53D1\u5E03\u3002\u56FE\u7247\u53EF\u80FD\u5DF2\u4E0A\u4F20\u5230\u5E73\u53F0\uFF0C\u90E8\u5206\u5E73\u53F0\u4F1A\u7559\u4E0B\u4E2D\u95F4\u8349\u7A3F\uFF0C\u516C\u4F17\u53F7\u3001\u641C\u72D0\u53F7\u4E0D\u53C2\u4E0E\u3002\u7ED3\u679C publicationState=rehearsed \u4E0D\u4EE3\u8868\u5DF2\u53D1\u5E03\uFF0C\u4E0D\u5F97\u544A\u8BC9\u7528\u6237\u5185\u5BB9\u5DF2\u53D1\u51FA\uFF0C\u4E5F\u4E0D\u8981\u81EA\u884C\u63A5\u7740\u53D1\u5E03\u3002",
|
|
50408
50409
|
inputSchema: unifiedInput,
|
|
@@ -50518,11 +50519,11 @@ var import_node_child_process = require("child_process");
|
|
|
50518
50519
|
var import_promises5 = require("fs/promises");
|
|
50519
50520
|
var import_node_os3 = require("os");
|
|
50520
50521
|
var import_node_path5 = require("path");
|
|
50521
|
-
var
|
|
50522
|
-
var
|
|
50522
|
+
var NPX_ARGS = ["-y", "--prefer-online", "writedock-mcp@latest"];
|
|
50523
|
+
var serverCommand = (platform) => platform === "win32" ? { command: "cmd", args: ["/c", "npx", ...NPX_ARGS] } : { command: "npx", args: NPX_ARGS };
|
|
50523
50524
|
async function readIfPresent(path) {
|
|
50524
50525
|
try {
|
|
50525
|
-
return await (0, import_promises5.readFile)(path, "utf8");
|
|
50526
|
+
return (await (0, import_promises5.readFile)(path, "utf8")).replace(/^\uFEFF/, "");
|
|
50526
50527
|
} catch (error2) {
|
|
50527
50528
|
if (error2.code === "ENOENT") return void 0;
|
|
50528
50529
|
throw error2;
|
|
@@ -50533,12 +50534,13 @@ async function save(path, content, previous) {
|
|
|
50533
50534
|
if (previous !== void 0) await (0, import_promises5.copyFile)(path, `${path}.writedock-backup`);
|
|
50534
50535
|
await (0, import_promises5.writeFile)(path, content);
|
|
50535
50536
|
}
|
|
50536
|
-
async function configureCodex(path) {
|
|
50537
|
+
async function configureCodex(path, platform = process.platform) {
|
|
50537
50538
|
const previous = await readIfPresent(path);
|
|
50538
50539
|
if (previous !== void 0 && /^\s*\[mcp_servers\.writedock\]/m.test(previous)) return "present";
|
|
50540
|
+
const { command, args } = serverCommand(platform);
|
|
50539
50541
|
const entry = `[mcp_servers.writedock]
|
|
50540
|
-
command = "${
|
|
50541
|
-
args = ${JSON.stringify(
|
|
50542
|
+
command = "${command}"
|
|
50543
|
+
args = ${JSON.stringify(args)}
|
|
50542
50544
|
startup_timeout_sec = 120
|
|
50543
50545
|
`;
|
|
50544
50546
|
await save(path, previous ? `${previous.replace(/\s*$/, "")}
|
|
@@ -50546,11 +50548,11 @@ startup_timeout_sec = 120
|
|
|
50546
50548
|
${entry}` : entry, previous);
|
|
50547
50549
|
return "added";
|
|
50548
50550
|
}
|
|
50549
|
-
async function configureJsonClient(path) {
|
|
50551
|
+
async function configureJsonClient(path, platform = process.platform) {
|
|
50550
50552
|
const previous = await readIfPresent(path);
|
|
50551
50553
|
const config2 = previous?.trim() ? JSON.parse(previous) : {};
|
|
50552
50554
|
if (config2.mcpServers?.writedock) return "present";
|
|
50553
|
-
config2.mcpServers = { ...config2.mcpServers, writedock:
|
|
50555
|
+
config2.mcpServers = { ...config2.mcpServers, writedock: serverCommand(platform) };
|
|
50554
50556
|
await save(path, `${JSON.stringify(config2, null, 2)}
|
|
50555
50557
|
`, previous);
|
|
50556
50558
|
return "added";
|
|
@@ -50566,19 +50568,20 @@ function revealFolder(path) {
|
|
|
50566
50568
|
} catch {
|
|
50567
50569
|
}
|
|
50568
50570
|
}
|
|
50569
|
-
async function runSetup(home = (0, import_node_os3.homedir)(), log = console.log) {
|
|
50571
|
+
async function runSetup(home = (0, import_node_os3.homedir)(), log = console.log, platform = process.platform) {
|
|
50572
|
+
const claudeDesktop = platform === "win32" ? (0, import_node_path5.join)(process.env.APPDATA || (0, import_node_path5.join)(home, "AppData/Roaming"), "Claude/claude_desktop_config.json") : platform === "darwin" ? (0, import_node_path5.join)(home, "Library/Application Support/Claude/claude_desktop_config.json") : (0, import_node_path5.join)(home, ".config/Claude/claude_desktop_config.json");
|
|
50570
50573
|
const clients = [
|
|
50571
50574
|
{ name: "Codex", path: (0, import_node_path5.join)(home, ".codex/config.toml"), detect: (0, import_node_path5.join)(home, ".codex/config.toml"), configure: configureCodex },
|
|
50572
50575
|
{ name: "Claude Code", path: (0, import_node_path5.join)(home, ".claude.json"), detect: (0, import_node_path5.join)(home, ".claude.json"), configure: configureJsonClient },
|
|
50573
50576
|
{ name: "Cursor", path: (0, import_node_path5.join)(home, ".cursor/mcp.json"), detect: (0, import_node_path5.join)(home, ".cursor/mcp.json"), configure: configureJsonClient },
|
|
50574
|
-
{ name: "Claude Desktop", path:
|
|
50577
|
+
{ name: "Claude Desktop", path: claudeDesktop, detect: claudeDesktop, configure: configureJsonClient }
|
|
50575
50578
|
];
|
|
50576
50579
|
let found = 0;
|
|
50577
50580
|
for (const client of clients) {
|
|
50578
50581
|
if (!await exists(client.detect)) continue;
|
|
50579
50582
|
found++;
|
|
50580
50583
|
try {
|
|
50581
|
-
const state = await client.configure(client.path);
|
|
50584
|
+
const state = await client.configure(client.path, platform);
|
|
50582
50585
|
log(state === "added" ? `\u2713 ${client.name}\uFF1A\u5DF2\u6DFB\u52A0 WriteDock\uFF08\u539F\u914D\u7F6E\u5907\u4EFD\u4E3A .writedock-backup\uFF09` : `\u2713 ${client.name}\uFF1A\u5DF2\u6709 WriteDock \u914D\u7F6E\uFF0C\u672A\u6539\u52A8`);
|
|
50583
50586
|
} catch (error2) {
|
|
50584
50587
|
log(`\u2717 ${client.name}\uFF1A\u914D\u7F6E\u5931\u8D25\uFF08${error2 instanceof Error ? error2.message : String(error2)}\uFF09\uFF0C\u539F\u6587\u4EF6\u672A\u6539\u52A8`);
|
|
@@ -50586,7 +50589,7 @@ async function runSetup(home = (0, import_node_os3.homedir)(), log = console.log
|
|
|
50586
50589
|
}
|
|
50587
50590
|
if (!found) {
|
|
50588
50591
|
log("\u6CA1\u6709\u627E\u5230\u5DF2\u77E5\u7684 agent \u5BA2\u6237\u7AEF\u3002\u8BF7\u5728\u4F60\u7684\u5BA2\u6237\u7AEF\u91CC\u6DFB\u52A0\u8FD9\u4E2A MCP \u670D\u52A1\uFF1A");
|
|
50589
|
-
log(JSON.stringify({ mcpServers: { writedock:
|
|
50592
|
+
log(JSON.stringify({ mcpServers: { writedock: serverCommand(platform) } }, null, 2));
|
|
50590
50593
|
}
|
|
50591
50594
|
const bundle = bundledExtension();
|
|
50592
50595
|
const loaded = bundle ? await findInstalledExtension().catch(() => void 0) : void 0;
|
package/package.json
CHANGED
package/source/package.json
CHANGED
|
@@ -22,6 +22,25 @@ describe('setup', () => {
|
|
|
22
22
|
expect(await readFile(join(home, '.codex/config.toml.writedock-backup'), 'utf8')).toBe(original)
|
|
23
23
|
})
|
|
24
24
|
|
|
25
|
+
it('starts npx through cmd on Windows, and finds Claude Desktop in its Windows folder', async () => {
|
|
26
|
+
const appData = process.env.APPDATA
|
|
27
|
+
delete process.env.APPDATA
|
|
28
|
+
try {
|
|
29
|
+
await mkdir(join(home, '.codex'))
|
|
30
|
+
await writeFile(join(home, '.codex/config.toml'), 'model = "x"\r\n')
|
|
31
|
+
const desktop = join(home, 'AppData/Roaming/Claude')
|
|
32
|
+
await mkdir(desktop, { recursive: true })
|
|
33
|
+
await writeFile(join(desktop, 'claude_desktop_config.json'), '\uFEFF{"theme":"dark"}')
|
|
34
|
+
expect(await runSetup(home, line => log.push(line), 'win32')).toBe(2)
|
|
35
|
+
const codex = await readFile(join(home, '.codex/config.toml'), 'utf8')
|
|
36
|
+
expect(codex).toContain('command = "cmd"')
|
|
37
|
+
expect(codex).toContain('args = ["/c","npx","-y","--prefer-online","writedock-mcp@latest"]')
|
|
38
|
+
const config = JSON.parse(await readFile(join(desktop, 'claude_desktop_config.json'), 'utf8'))
|
|
39
|
+
expect(config.theme).toBe('dark')
|
|
40
|
+
expect(config.mcpServers.writedock).toEqual({ command: 'cmd', args: ['/c', 'npx', '-y', '--prefer-online', 'writedock-mcp@latest'] })
|
|
41
|
+
} finally { if (appData !== undefined) process.env.APPDATA = appData }
|
|
42
|
+
})
|
|
43
|
+
|
|
25
44
|
it('leaves an existing entry alone, including its custom port and token', async () => {
|
|
26
45
|
await mkdir(join(home, '.codex'))
|
|
27
46
|
const original = '[mcp_servers.writedock]\ncommand = "node"\n\n[mcp_servers.writedock.env]\nWRITEDOCK_PORT = "9700"\n'
|
package/source/src/bridge.ts
CHANGED
|
@@ -253,7 +253,7 @@ export class LocalBridge {
|
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
private async requestConnected(method: string, params: Record<string, unknown>): Promise<unknown> {
|
|
256
|
-
if (this.client?.readyState !== WebSocket.OPEN) throw new Error('
|
|
256
|
+
if (this.client?.readyState !== WebSocket.OPEN) throw new Error('扩展未连接,这次没有发布。请让用户打开 Chrome 或 Edge,连接过的扩展通常会自动连上,然后再试;如果还是不行,打开 WriteDock 扩展点“连接”。无需重装 MCP。')
|
|
257
257
|
const id = randomUUID()
|
|
258
258
|
return new Promise((resolve, reject) => {
|
|
259
259
|
const timer = setTimeout(() => {
|
package/source/src/server.ts
CHANGED
|
@@ -26,7 +26,7 @@ const postSource = {
|
|
|
26
26
|
base_directory: z.string().optional().describe('使用相对素材路径时,明确指定素材目录'),
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
const agentWorkflow = 'Agent 只通过 WriteDock MCP 投稿和查询回执,不依赖也不要调用浏览器、Playwright 或其他网页工具。按结果的 next_action 操作:poll_task 用原 task_id/request_id 查询 get_sync_task;report_result 直接向用户汇报逐平台状态和链接。submitted/published 已有扩展确认,不再打开网页验证;验证码、图形验证、协议、发布设置或 unknown/interrupted 都将原因和链接交给用户自行点击处理,不自动重发、不换 request_id
|
|
29
|
+
const agentWorkflow = 'Agent 只通过 WriteDock MCP 投稿和查询回执,不依赖也不要调用浏览器、Playwright 或其他网页工具。按结果的 next_action 操作:poll_task 用原 task_id/request_id 查询 get_sync_task;report_result 直接向用户汇报逐平台状态和链接。submitted/published 已有扩展确认,不再打开网页验证;验证码、图形验证、协议、发布设置或 unknown/interrupted 都将原因和链接交给用户自行点击处理,不自动重发、不换 request_id。人工接管不代表已经发布。唯一例外是 user_actions 的 type=old_draft:平台有一份用户上次没发完的内容,这篇尚未提交。必须先问用户是否放弃那份旧内容,不得替用户决定;用户明确同意后,才用新的 request_id 只对该平台再调用 publish,并传 discard_old_drafts;用户不同意就保留旧内容,告诉用户这个平台没有发。'
|
|
30
30
|
|
|
31
31
|
export function createMcpServer(bridge: LocalBridge) {
|
|
32
32
|
const server = new McpServer({ name: 'writedock', version: mcpPackage.version }, {
|
|
@@ -92,9 +92,9 @@ export function createMcpServer(bridge: LocalBridge) {
|
|
|
92
92
|
}
|
|
93
93
|
server.registerTool('publish', {
|
|
94
94
|
description: agentWorkflow + '首选的发布工具:一次调用发布到用户指定的多个平台。只有用户明确要求发布并指定平台时才调用。按平台依次执行,某个平台未登录、不能接收内容或失败,只跳过该平台,其他平台继续。遇到验证码时平台进入 waiting_verification,立即把原因和链接告诉用户,由用户手动完成,继续查询即可,不要重发。任务结束后按 get_sync_task 的 summary 逐平台告诉用户:哪些成功,哪些未登录(not_logged_in)等。立即返回 task_id,再用 get_sync_task 查询;results 是逐平台结果,warnings 说明为平台做的调整。必须使用稳定 request_id,相同 ID 不会重复提交。部分平台失败时,只针对失败平台用新 request_id 重新提交,unknown 的平台不要重发。旧版扩展不支持时会报错,不会发布。',
|
|
95
|
-
inputSchema: { ...unifiedInput, request_id: requestId },
|
|
95
|
+
inputSchema: { ...unifiedInput, request_id: requestId, discard_old_drafts: z.array(z.enum(['douyin'])).optional().describe('只有用户明确同意放弃平台上次没发完的内容时才传,如 ["douyin"]。放弃后无法恢复。') },
|
|
96
96
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
97
|
-
}, args => wrap(async () => bridge.request('publish', { requestId: args.request_id, ...await unifiedParams(args) })))
|
|
97
|
+
}, args => wrap(async () => bridge.request('publish', { requestId: args.request_id, ...(args.discard_old_drafts ? { discardOldDrafts: args.discard_old_drafts } : {}), ...await unifiedParams(args) })))
|
|
98
98
|
server.registerTool('rehearse_publish', {
|
|
99
99
|
description: '与 publish 参数相同的演练:逐平台打开原生页面,填好内容、上传素材,走到点击发布之前停下,不提交发布。无需 request_id,不影响之后的真实发布。图片可能已上传到平台,部分平台会留下中间草稿,公众号、搜狐号不参与。结果 publicationState=rehearsed 不代表已发布,不得告诉用户内容已发出,也不要自行接着发布。',
|
|
100
100
|
inputSchema: unifiedInput,
|
package/source/src/setup.ts
CHANGED
|
@@ -4,11 +4,15 @@ import { homedir } from 'node:os'
|
|
|
4
4
|
import { dirname, join } from 'node:path'
|
|
5
5
|
import { bundledExtension, defaultExtensionDirectory, findInstalledExtension, installExtensionFiles } from './extension-update'
|
|
6
6
|
|
|
7
|
-
const
|
|
8
|
-
|
|
7
|
+
const NPX_ARGS = ['-y', '--prefer-online', 'writedock-mcp@latest']
|
|
8
|
+
/** On Windows npx is a .cmd script, which clients that start programs directly cannot run. */
|
|
9
|
+
const serverCommand = (platform: NodeJS.Platform) => platform === 'win32'
|
|
10
|
+
? { command: 'cmd', args: ['/c', 'npx', ...NPX_ARGS] }
|
|
11
|
+
: { command: 'npx', args: NPX_ARGS }
|
|
9
12
|
|
|
10
13
|
async function readIfPresent(path: string) {
|
|
11
|
-
|
|
14
|
+
// Windows editors may save a BOM, which JSON.parse rejects.
|
|
15
|
+
try { return (await readFile(path, 'utf8')).replace(/^\uFEFF/, '') }
|
|
12
16
|
catch (error) { if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined; throw error }
|
|
13
17
|
}
|
|
14
18
|
|
|
@@ -19,19 +23,20 @@ async function save(path: string, content: string, previous: string | undefined)
|
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
/** Keeps the user's other settings; an existing WriteDock entry stays, because it may hold a custom port or data directory. */
|
|
22
|
-
export async function configureCodex(path: string): Promise<'added' | 'present'> {
|
|
26
|
+
export async function configureCodex(path: string, platform: NodeJS.Platform = process.platform): Promise<'added' | 'present'> {
|
|
23
27
|
const previous = await readIfPresent(path)
|
|
24
28
|
if (previous !== undefined && /^\s*\[mcp_servers\.writedock\]/m.test(previous)) return 'present'
|
|
25
|
-
const
|
|
29
|
+
const { command, args } = serverCommand(platform)
|
|
30
|
+
const entry = `[mcp_servers.writedock]\ncommand = "${command}"\nargs = ${JSON.stringify(args)}\nstartup_timeout_sec = 120\n`
|
|
26
31
|
await save(path, previous ? `${previous.replace(/\s*$/, '')}\n\n${entry}` : entry, previous)
|
|
27
32
|
return 'added'
|
|
28
33
|
}
|
|
29
34
|
|
|
30
|
-
export async function configureJsonClient(path: string): Promise<'added' | 'present'> {
|
|
35
|
+
export async function configureJsonClient(path: string, platform: NodeJS.Platform = process.platform): Promise<'added' | 'present'> {
|
|
31
36
|
const previous = await readIfPresent(path)
|
|
32
37
|
const config = previous?.trim() ? JSON.parse(previous) as { mcpServers?: Record<string, unknown> } : {}
|
|
33
38
|
if (config.mcpServers?.writedock) return 'present'
|
|
34
|
-
config.mcpServers = { ...config.mcpServers, writedock:
|
|
39
|
+
config.mcpServers = { ...config.mcpServers, writedock: serverCommand(platform) }
|
|
35
40
|
await save(path, `${JSON.stringify(config, null, 2)}\n`, previous)
|
|
36
41
|
return 'added'
|
|
37
42
|
}
|
|
@@ -45,25 +50,30 @@ function revealFolder(path: string) {
|
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
/** Only clients already on this computer are configured, so setup never creates settings for an app the user does not have. */
|
|
48
|
-
export async function runSetup(home = homedir(), log: (line: string) => void = console.log) {
|
|
49
|
-
const
|
|
53
|
+
export async function runSetup(home = homedir(), log: (line: string) => void = console.log, platform: NodeJS.Platform = process.platform) {
|
|
54
|
+
const claudeDesktop = platform === 'win32'
|
|
55
|
+
? join(process.env.APPDATA || join(home, 'AppData/Roaming'), 'Claude/claude_desktop_config.json')
|
|
56
|
+
: platform === 'darwin'
|
|
57
|
+
? join(home, 'Library/Application Support/Claude/claude_desktop_config.json')
|
|
58
|
+
: join(home, '.config/Claude/claude_desktop_config.json')
|
|
59
|
+
const clients: { name: string; path: string; detect: string; configure: (path: string, platform: NodeJS.Platform) => Promise<'added' | 'present'> }[] = [
|
|
50
60
|
{ name: 'Codex', path: join(home, '.codex/config.toml'), detect: join(home, '.codex/config.toml'), configure: configureCodex },
|
|
51
61
|
{ name: 'Claude Code', path: join(home, '.claude.json'), detect: join(home, '.claude.json'), configure: configureJsonClient },
|
|
52
62
|
{ name: 'Cursor', path: join(home, '.cursor/mcp.json'), detect: join(home, '.cursor/mcp.json'), configure: configureJsonClient },
|
|
53
|
-
{ name: 'Claude Desktop', path:
|
|
63
|
+
{ name: 'Claude Desktop', path: claudeDesktop, detect: claudeDesktop, configure: configureJsonClient },
|
|
54
64
|
]
|
|
55
65
|
let found = 0
|
|
56
66
|
for (const client of clients) {
|
|
57
67
|
if (!await exists(client.detect)) continue
|
|
58
68
|
found++
|
|
59
69
|
try {
|
|
60
|
-
const state = await client.configure(client.path)
|
|
70
|
+
const state = await client.configure(client.path, platform)
|
|
61
71
|
log(state === 'added' ? `✓ ${client.name}:已添加 WriteDock(原配置备份为 .writedock-backup)` : `✓ ${client.name}:已有 WriteDock 配置,未改动`)
|
|
62
72
|
} catch (error) { log(`✗ ${client.name}:配置失败(${error instanceof Error ? error.message : String(error)}),原文件未改动`) }
|
|
63
73
|
}
|
|
64
74
|
if (!found) {
|
|
65
75
|
log('没有找到已知的 agent 客户端。请在你的客户端里添加这个 MCP 服务:')
|
|
66
|
-
log(JSON.stringify({ mcpServers: { writedock:
|
|
76
|
+
log(JSON.stringify({ mcpServers: { writedock: serverCommand(platform) } }, null, 2))
|
|
67
77
|
}
|
|
68
78
|
// Keep whichever folder the browser already loads, so a repeat setup does not ask for another manual load.
|
|
69
79
|
const bundle = bundledExtension()
|
package/source/tsup.config.ts
CHANGED
|
@@ -2,5 +2,7 @@ import { defineConfig } from 'tsup'
|
|
|
2
2
|
export default defineConfig({
|
|
3
3
|
entry: ['src/index.ts'], format: ['cjs'], platform: 'node', target: 'node22',
|
|
4
4
|
outExtension: () => ({ js: '.cjs' }), clean: true, sourcemap: false, metafile: true,
|
|
5
|
+
// Runs before any bundled code, so an old Node.js gets a plain sentence instead of a stack trace.
|
|
6
|
+
banner: { js: `if (Number(process.versions.node.split('.')[0]) < 22) { console.error('WriteDock 需要 Node.js 22 或更高版本,当前是 ' + process.version + '。请安装 https://nodejs.org 的 LTS 版,然后重启 agent。'); process.exit(1) }` },
|
|
5
7
|
noExternal: [/.*/], external: ['bufferutil', 'utf-8-validate'],
|
|
6
8
|
})
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import{p as oe}from"./purify.es-1yEQDPgc.js";import{r as i,j as e,c as de}from"./react-vendor-BrXlnJmF.js";import{S as B,s as P,e as he}from"./task-utils-DwzGcJkm.js";/**
|
|
2
|
-
* @license lucide-react v0.303.0 - ISC
|
|
3
|
-
*
|
|
4
|
-
* This source code is licensed under the ISC license.
|
|
5
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
6
|
-
*/var ue={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};/**
|
|
7
|
-
* @license lucide-react v0.303.0 - ISC
|
|
8
|
-
*
|
|
9
|
-
* This source code is licensed under the ISC license.
|
|
10
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
11
|
-
*/const xe=t=>t.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase().trim(),m=(t,d)=>{const l=i.forwardRef(({color:g="currentColor",size:o=24,strokeWidth:j=2,absoluteStrokeWidth:x,className:y="",children:u,...N},c)=>i.createElement("svg",{ref:c,...ue,width:o,height:o,stroke:g,strokeWidth:x?Number(j)*24/Number(o):j,className:["lucide",`lucide-${xe(t)}`,y].join(" "),...N},[...d.map(([v,k])=>i.createElement(v,k)),...Array.isArray(u)?u:[u]]));return l.displayName=`${t}`,l};/**
|
|
12
|
-
* @license lucide-react v0.303.0 - ISC
|
|
13
|
-
*
|
|
14
|
-
* This source code is licensed under the ISC license.
|
|
15
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
16
|
-
*/const Z=m("Check",[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]]);/**
|
|
17
|
-
* @license lucide-react v0.303.0 - ISC
|
|
18
|
-
*
|
|
19
|
-
* This source code is licensed under the ISC license.
|
|
20
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
21
|
-
*/const pe=m("Copy",[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]]);/**
|
|
22
|
-
* @license lucide-react v0.303.0 - ISC
|
|
23
|
-
*
|
|
24
|
-
* This source code is licensed under the ISC license.
|
|
25
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
26
|
-
*/const q=m("ExternalLink",[["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6",key:"a6xqqp"}],["polyline",{points:"15 3 21 3 21 9",key:"mznyad"}],["line",{x1:"10",x2:"21",y1:"14",y2:"3",key:"18c3s4"}]]);/**
|
|
27
|
-
* @license lucide-react v0.303.0 - ISC
|
|
28
|
-
*
|
|
29
|
-
* This source code is licensed under the ISC license.
|
|
30
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
31
|
-
*/const me=m("Eye",[["path",{d:"M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z",key:"rwhkz3"}],["circle",{cx:"12",cy:"12",r:"3",key:"1v7zrd"}]]);/**
|
|
32
|
-
* @license lucide-react v0.303.0 - ISC
|
|
33
|
-
*
|
|
34
|
-
* This source code is licensed under the ISC license.
|
|
35
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
36
|
-
*/const U=m("FileText",[["path",{d:"M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z",key:"1nnpy2"}],["polyline",{points:"14 2 14 8 20 8",key:"1ew0cm"}],["line",{x1:"16",x2:"8",y1:"13",y2:"13",key:"14keom"}],["line",{x1:"16",x2:"8",y1:"17",y2:"17",key:"17nazh"}],["line",{x1:"10",x2:"8",y1:"9",y2:"9",key:"1a5vjj"}]]);/**
|
|
37
|
-
* @license lucide-react v0.303.0 - ISC
|
|
38
|
-
*
|
|
39
|
-
* This source code is licensed under the ISC license.
|
|
40
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
41
|
-
*/const O=m("History",[["path",{d:"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",key:"1357e3"}],["path",{d:"M3 3v5h5",key:"1xhq8a"}],["path",{d:"M12 7v5l4 2",key:"1fdv2h"}]]);/**
|
|
42
|
-
* @license lucide-react v0.303.0 - ISC
|
|
43
|
-
*
|
|
44
|
-
* This source code is licensed under the ISC license.
|
|
45
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
46
|
-
*/const fe=m("Info",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 16v-4",key:"1dtifu"}],["path",{d:"M12 8h.01",key:"e9boi3"}]]);/**
|
|
47
|
-
* @license lucide-react v0.303.0 - ISC
|
|
48
|
-
*
|
|
49
|
-
* This source code is licensed under the ISC license.
|
|
50
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
51
|
-
*/const G=m("Link2",[["path",{d:"M9 17H7A5 5 0 0 1 7 7h2",key:"8i5ue5"}],["path",{d:"M15 7h2a5 5 0 1 1 0 10h-2",key:"1b9ql8"}],["line",{x1:"8",x2:"16",y1:"12",y2:"12",key:"1jonct"}]]);/**
|
|
52
|
-
* @license lucide-react v0.303.0 - ISC
|
|
53
|
-
*
|
|
54
|
-
* This source code is licensed under the ISC license.
|
|
55
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
56
|
-
*/const W=m("Loader2",[["path",{d:"M21 12a9 9 0 1 1-6.219-8.56",key:"13zald"}]]);/**
|
|
57
|
-
* @license lucide-react v0.303.0 - ISC
|
|
58
|
-
*
|
|
59
|
-
* This source code is licensed under the ISC license.
|
|
60
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
61
|
-
*/const K=m("RefreshCw",[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",key:"v9h5vc"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",key:"3uifl3"}],["path",{d:"M8 16H3v5",key:"1cv678"}]]);/**
|
|
62
|
-
* @license lucide-react v0.303.0 - ISC
|
|
63
|
-
*
|
|
64
|
-
* This source code is licensed under the ISC license.
|
|
65
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
66
|
-
*/const je=m("Trash2",[["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6",key:"4alrt4"}],["path",{d:"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2",key:"v07s0e"}],["line",{x1:"10",x2:"10",y1:"11",y2:"17",key:"1uufr5"}],["line",{x1:"14",x2:"14",y1:"11",y2:"17",key:"xtxkd"}]]);/**
|
|
67
|
-
* @license lucide-react v0.303.0 - ISC
|
|
68
|
-
*
|
|
69
|
-
* This source code is licensed under the ISC license.
|
|
70
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
71
|
-
*/const ye=m("Users",[["path",{d:"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2",key:"1yyitq"}],["circle",{cx:"9",cy:"7",r:"4",key:"nufk8"}],["path",{d:"M22 21v-2a4 4 0 0 0-3-3.87",key:"kshegd"}],["path",{d:"M16 3.13a4 4 0 0 1 0 7.75",key:"1da9ce"}]]);/**
|
|
72
|
-
* @license lucide-react v0.303.0 - ISC
|
|
73
|
-
*
|
|
74
|
-
* This source code is licensed under the ISC license.
|
|
75
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
76
|
-
*/const J=m("X",[["path",{d:"M18 6 6 18",key:"1bl5f8"}],["path",{d:"m6 6 12 12",key:"d8bk6v"}]]);async function ke(){return(await chrome.storage.local.get(B.draftIndex))[B.draftIndex]||[]}async function ve(t){const d=`writedockDraft:${t}`;return(await chrome.storage.local.get(d))[d]||null}async function Ne(t){return E("DELETE_DRAFT",{draftId:t})}async function E(t,d={}){const l=await chrome.runtime.sendMessage({type:t,...d});if(l!=null&&l.error)throw new Error(l.error);if(!l||!("data"in l))throw new Error("扩展后台未响应,请在扩展管理页重新加载 WriteDock");return l.data}function ge({bridge:t,busy:d,syncing:l,update:g,connect:o,updateExtension:j,copy:x}){const[y,u]=i.useState(String(t.port));i.useEffect(()=>u(String(t.port)),[t.port]);const N="帮我安装 WriteDock:运行 npx -y writedock-mcp@latest setup,然后告诉我怎么重启你。",c=[t.mcpVersion?`服务版本 v${t.mcpVersion}`:"",t.connected&&t.mcpLatestVersion?`最新 v${t.mcpLatestVersion}`:""].filter(Boolean).join(" · ");return e.jsxs("div",{className:"connection-page",children:[t.connected?e.jsxs("section",{className:"panel",children:[e.jsx("div",{className:"panel-heading",children:e.jsxs("div",{children:[e.jsxs("h2",{children:[e.jsx(Z,{size:16})," 已连接"]}),e.jsxs("p",{children:["告诉你的 agent 要写什么、发到哪些平台就行。",c&&` ${c}`]})]})}),t.mcpUpdateAvailable&&e.jsx("p",{className:"help-text",role:"status",children:"有新版本。等发布结束后重启你的 agent 就会更新,不用重新连接。"})]}):t.mcpFound?e.jsxs("section",{className:"panel",children:[e.jsx("h2",{children:"发现本机的 WriteDock 服务"}),e.jsxs("p",{className:"help-text",children:[t.mcpFoundVersion?`服务版本 v${t.mcpFoundVersion}。`:"","点一下就能连上,以后会自动连接。"]}),e.jsx("div",{className:"settings-actions",children:e.jsx("button",{className:"primary-button",disabled:d||l,onClick:()=>void o(),children:d?"正在连接…":"连接"})})]}):e.jsxs("section",{className:"panel",children:[e.jsx("h2",{children:"把这句话发给你的 agent"}),e.jsx("p",{className:"help-text",children:"它会帮你装好服务。需要电脑上有 Node.js 22 或更高版本。"}),e.jsx("pre",{tabIndex:0,className:"setup-sentence",children:N}),e.jsx("div",{className:"settings-actions",children:e.jsxs("button",{className:"primary-button",onClick:()=>void x(N),children:[e.jsx(pe,{size:14}),"复制这句话"]})}),e.jsx("p",{className:"help-text",children:"agent 重启后,这里会出现“连接”按钮。"})]}),t.connected&&t.extensionUpdateVersion&&e.jsxs("section",{className:"panel",children:[e.jsxs("h2",{children:["扩展有新版本 v",t.extensionUpdateVersion]}),e.jsx("p",{className:"help-text",children:"点一下就能更新,连接和稿件都会保留。"}),e.jsx("div",{className:"settings-actions",children:e.jsx("button",{className:"primary-button",disabled:d||l,onClick:()=>void j(),children:d?"正在更新…":"更新扩展"})}),l&&e.jsx("p",{className:"help-text",children:"发布结束后才能更新。"})]}),e.jsxs("section",{className:"panel compact",children:[e.jsx("h2",{children:"怎么用"}),e.jsxs("ol",{children:[e.jsx("li",{children:"在「平台登录」里确认要发布的平台已登录。"}),e.jsx("li",{children:"告诉 agent 写什么、发到哪些平台。"}),e.jsx("li",{children:"遇到验证码时,agent 会告诉你去哪个页面完成。"})]})]}),e.jsxs("details",{className:"panel developer-settings",children:[e.jsx("summary",{children:"高级设置"}),e.jsx("p",{className:"help-text",children:"通常不需要动这里。"}),e.jsxs("label",{children:["启用自动连接",e.jsx("button",{role:"switch","aria-label":"启用 MCP 连接","aria-checked":t.enabled,className:`switch ${t.enabled?"on":""}`,disabled:d||l,onClick:()=>g({enabled:!t.enabled}),children:e.jsx("span",{})})]}),e.jsx("div",{className:"settings-grid",children:e.jsxs("label",{children:["连接端口",e.jsx("input",{"aria-label":"连接端口",inputMode:"numeric",value:y,onChange:v=>u(v.target.value)})]})}),e.jsx("div",{className:"settings-actions",children:e.jsx("button",{className:"secondary-button",disabled:d||l||y===String(t.port),onClick:()=>g({port:Number(y)}),children:"保存端口"})})]})]})}const X=t=>new Intl.DateTimeFormat("zh-CN",{month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",hour12:!1}).format(t),M=t=>t.trim()||"未命名稿件",we={syncing:"正在保存",completed:"已保存草稿",partial:"部分成功",failed:"失败",interrupted:"中断了",unknown:"请去平台确认",needs_user_action:"需要你处理",rehearsed:"演练完成"},Q={syncing:"正在发布",completed:"已发布",partial:"部分成功",failed:"发布失败",interrupted:"中断了",unknown:"请去平台确认",needs_user_action:"需要你处理",rehearsed:"演练完成"},be={...Q,syncing:"正在演练",completed:"演练完成",partial:"部分通过",failed:"演练失败"},Ee={pending:"排队中",checking_auth:"检查登录",uploading_images:"上传图片",transferring_media:"读取素材",uploading_media:"上传素材",preparing_article:"准备内容",publishing:"发布中",verifying:"等待平台确认",saving:"保存草稿",completed:"完成",failed:"失败",unknown:"请去平台确认",needs_user_action:"需要你处理",waiting_verification:"等你完成验证"},Te=t=>(t.videos||[]).reduce((d,l)=>d.replace(l.marker,`视频:${he(l.asset.name)}`),t.html),Ce=t=>t.publicationState==="rehearsed"?"演练通过,未发布":t.errorCode==="not_logged_in"?"未登录,已跳过":t.publicationState==="needs_user_action"?"需要你处理":t.publicationState==="unknown"?"请去平台确认":t.success?t.draftOnly===!1?"已发布":"已保存草稿":"失败";function Y({title:t,children:d,close:l,wide:g=!1}){const o=i.useRef(null),j=i.useRef(l);return j.current=l,i.useEffect(()=>{var u,N;const x=document.activeElement;(N=(u=o.current)==null?void 0:u.querySelector("button"))==null||N.focus();const y=c=>{var w;if(c.key==="Escape"){c.preventDefault(),j.current();return}if(c.key!=="Tab")return;const v=Array.from(((w=o.current)==null?void 0:w.querySelectorAll('button:not(:disabled), a[href], input, [tabindex="0"]'))||[]),k=v[0],T=v.at(-1);c.shiftKey&&document.activeElement===k?(c.preventDefault(),T==null||T.focus()):!c.shiftKey&&document.activeElement===T&&(c.preventDefault(),k==null||k.focus())};return document.addEventListener("keydown",y),()=>{document.removeEventListener("keydown",y),x==null||x.focus()}},[]),e.jsx("div",{className:"modal-backdrop",onMouseDown:x=>{x.target===x.currentTarget&&l()},children:e.jsxs("section",{className:`modal ${g?"wide":""}`,ref:o,role:"dialog","aria-modal":"true","aria-label":t,children:[e.jsxs("header",{children:[e.jsx("h2",{children:t}),e.jsx("button",{className:"icon-button","aria-label":"关闭弹窗",onClick:l,children:e.jsx(J,{size:18})})]}),d]})})}function _e(){const[t,d]=i.useState("tasks"),[l,g]=i.useState([]),[o,j]=i.useState(null),[x,y]=i.useState([]),[u,N]=i.useState([]),[c,v]=i.useState(null),[k,T]=i.useState(!1),[w,b]=i.useState(!1),[_,ee]=i.useState(!1),[L,p]=i.useState(""),[S,I]=i.useState(null),[F,z]=i.useState(null),R=i.useRef(!1),C=i.useCallback(async()=>{const[s,r,n]=await Promise.all([ke(),E("GET_SYNC_STATE"),E("GET_SYNC_HISTORY")]);g(s),j(r),y(n)},[]),A=i.useCallback(async()=>{const s=await E("GET_BRIDGE_STATE");return v(s),s},[]),$=i.useCallback(async(s=!1)=>{T(!0);try{N(await E("CHECK_ALL_AUTH",{forceRefresh:s}))}catch(r){p(r instanceof Error?r.message:"登录检测失败")}finally{T(!1)}},[]);i.useEffect(()=>{let s=!1;Promise.all([C(),A()]).then(([,a])=>{s||(a.connected||d("connection"),ee(!0),$())}).catch(a=>p(String(a.message||a)));const r=a=>(a.type==="TASK_UPDATED"&&a.task&&(j(a.task),a.task.status!=="syncing"&&C().catch(()=>{})),["DRAFT_SAVED","DRAFT_DELETED"].includes(a.type)&&(a.type==="DRAFT_DELETED"&&(I(h=>(h==null?void 0:h.id)===a.draftId?null:h),j(h=>(h==null?void 0:h.draftId)===a.draftId?null:h),y(h=>h.filter(f=>f.draftId!==a.draftId))),C().catch(()=>{})),a.type==="BRIDGE_STATUS_UPDATED"&&A().catch(()=>{}),!1);chrome.runtime.onMessage.addListener(r);const n=()=>{R.current&&(R.current=!1,$(!0))};return window.addEventListener("focus",n),()=>{s=!0,chrome.runtime.onMessage.removeListener(r),window.removeEventListener("focus",n)}},[C,A,$]),i.useEffect(()=>{if(t!=="connection"||c!=null&&c.connected)return;const s=setInterval(()=>{A().catch(()=>{})},3e3);return()=>clearInterval(s)},[t,c==null?void 0:c.connected,A]),i.useEffect(()=>{if(!L)return;const s=setTimeout(()=>p(""),7e3);return()=>clearTimeout(s)},[L]);const se=async s=>{b(!0);try{v(await E("SET_BRIDGE_CONFIG",{payload:s}))}catch(r){p(r instanceof Error?r.message:"连接设置保存失败")}finally{b(!1)}},te=async()=>{b(!0);try{return v(await E("CONNECT_BRIDGE")),!0}catch(s){return p(s instanceof Error?s.message:"连接失败"),!1}finally{b(!1)}},ae=async()=>{b(!0);try{await E("UPDATE_EXTENSION"),p("更新完成,正在重新加载")}catch(s){p(s instanceof Error?s.message:"更新失败")}finally{b(!1)}},H=(s,r=!1)=>{const n=P(s);n&&(async()=>{if(r){const a=new URL(n),f=(await chrome.tabs.query({})).find(le=>{try{const D=new URL(le.url||"");return D.origin===a.origin&&D.pathname===a.pathname&&(a.searchParams.has("article_id")?D.searchParams.get("article_id")===a.searchParams.get("article_id"):a.hash?D.hash===a.hash:D.href===a.href)}catch{return!1}});if(f!=null&&f.id){await chrome.tabs.update(f.id,{active:!0}),await chrome.windows.update(f.windowId,{focused:!0});return}}await chrome.tabs.create({url:n})})().catch(a=>p(a instanceof Error?a.message:"无法打开稿件页"))},ne=async s=>{try{await navigator.clipboard.writeText(s),p("已复制")}catch{p("复制失败,请选中文字后复制")}},V=o?[o,...x.filter(s=>s.id!==o.id)]:x,ce=s=>{var r;return((r=u.find(n=>n.id===s))==null?void 0:r.name)||s},re=u.filter(s=>s.isAuthenticated).length,ie=s=>e.jsxs("section",{className:"task-card","aria-label":`任务:${s.title}`,children:[e.jsxs("div",{className:"task-title",children:[e.jsxs("div",{children:[e.jsx("h3",{children:M(s.title)}),e.jsx("span",{children:X(s.startedAt)})]}),e.jsxs("span",{className:`task-status ${s.status}`,children:[s.status==="syncing"&&e.jsx(W,{size:13,className:"spin"}),(s.action==="rehearsal"?be:s.action==="publish"?Q:we)[s.status]]})]}),e.jsx("div",{className:"task-result-list",children:s.platforms.map(r=>{var h;const n=s.results.find(f=>f.platform===r),a=s.progress[r];return e.jsxs("div",{className:"task-result",children:[e.jsxs("div",{children:[e.jsx("strong",{children:ce(r)}),e.jsxs("span",{className:(n==null?void 0:n.publicationState)==="needs_user_action"||(a==null?void 0:a.stage)==="waiting_verification"?"manual-text":n!=null&&n.success||(n==null?void 0:n.publicationState)==="rehearsed"?"success-text":n?"error-text":"",children:[n?Ce(n):Ee[(a==null?void 0:a.stage)||"pending"],(a==null?void 0:a.stage)==="uploading_images"&&a.total?` ${a.current}/${a.total}`:"",(a==null?void 0:a.stage)==="transferring_media"&&a.total?` ${((a.current||0)/1048576).toFixed(1)} / ${(a.total/1048576).toFixed(1)} MB`:""]}),n&&(n.success||n.draftOnly===!1)&&P(((h=n.userAction)==null?void 0:h.url)||n.postUrl)&&e.jsxs("button",{className:"link-button",onClick:()=>{var f;return H(((f=n.userAction)==null?void 0:f.url)||n.postUrl,n.requiresUserAction===!0)},children:[n.requiresUserAction?"去页面处理":n.publicationState==="unknown"?"去平台确认":n.publicationState==="rehearsed"?"查看页面":n.draftOnly===!1?"查看":"打开草稿",e.jsx(q,{size:12})]})]}),(n==null?void 0:n.error)&&e.jsx("p",{className:"error-text",children:n.error}),(n==null?void 0:n.message)&&!n.success&&e.jsx("p",{children:n.message}),!n&&(a==null?void 0:a.message)&&e.jsx("p",{children:a.message})]},r)})}),s.error&&e.jsx("p",{className:"error-text task-error",children:s.error}),e.jsx("footer",{children:s.action==="rehearsal"?`演练 ${s.results.filter(r=>r.publicationState==="rehearsed").length} / ${s.platforms.length} 个平台通过`:`${s.results.filter(r=>r.success).length} / ${s.platforms.length} 个平台成功`})]},s.id);return e.jsxs("div",{className:"console-shell",children:[e.jsxs("header",{className:"app-header",children:[e.jsxs("div",{className:"brand",children:[e.jsx("img",{src:"/assets/icon-48.png",width:"26",height:"26",alt:""}),e.jsx("strong",{children:"WriteDock"})]}),e.jsxs("button",{className:`connection-indicator ${c!=null&&c.connected?"online":""}`,onClick:()=>d("connection"),children:[e.jsx("span",{}),c!=null&&c.connected?"已连接":"未连接"]})]}),e.jsxs("aside",{className:"navigation",children:[e.jsx("nav",{"aria-label":"控制台导航",children:[["tasks",O,"发布记录"],["accounts",ye,"平台登录"],["articles",U,"稿件"],["connection",G,"连接 agent"]].map(([s,r,n])=>e.jsxs("button",{className:t===s?"selected":"",onClick:()=>d(s),children:[e.jsx(r,{size:17}),n,s==="tasks"&&(o==null?void 0:o.status)==="syncing"&&e.jsx("span",{className:"nav-dot"}),s==="connection"&&((c==null?void 0:c.mcpUpdateAvailable)||(c==null?void 0:c.extensionUpdateVersion))&&e.jsx("span",{className:"nav-dot",title:"有新版本"})]},s))}),e.jsxs("footer",{children:[e.jsx(fe,{size:13}),e.jsxs("span",{children:["v",chrome.runtime.getManifest().version]})]})]}),e.jsxs("main",{className:"console-main",children:[e.jsxs("div",{className:"page-heading",children:[e.jsxs("div",{children:[e.jsx("h1",{children:{articles:"稿件",tasks:"发布记录",accounts:"平台登录",connection:"连接 agent"}[t]}),e.jsx("p",{children:{articles:"agent 写好的内容,可以在这里预览。",tasks:"每次发布到各平台的结果。",accounts:"在这个浏览器里登录要发布的平台。",connection:"连上后,你的 agent 就能把内容发到这个浏览器。"}[t]})]}),t==="articles"&&e.jsxs("button",{className:"secondary-button",onClick:()=>void C().catch(s=>p(s.message)),children:[e.jsx(K,{size:14}),"刷新"]}),t==="accounts"&&e.jsxs("button",{className:"secondary-button",disabled:k,onClick:()=>$(!0),children:[e.jsx(K,{size:14,className:k?"spin":""}),"重新检查"]})]}),!_&&e.jsxs("div",{className:"empty-state",children:[e.jsx(W,{className:"spin",size:24}),e.jsx("p",{children:"正在加载…"})]}),_&&t==="articles"&&e.jsxs(e.Fragment,{children:[!(c!=null&&c.connected)&&e.jsxs("div",{className:"connection-banner",children:[e.jsx(G,{size:17}),e.jsx("span",{children:c!=null&&c.mcpFound?"发现本机的 WriteDock 服务,点一下就能连接。":"还没有连接到你的 agent。"}),e.jsx("button",{className:"link-button",onClick:()=>d("connection"),children:"去连接"})]}),l.length===0?e.jsxs("div",{className:"empty-state bordered",children:[e.jsx(U,{size:30}),e.jsx("h2",{children:"还没有稿件"}),e.jsx("p",{children:"告诉你的 agent 要写什么,内容会出现在这里。"})]}):e.jsx("div",{className:"article-list",children:l.map(s=>e.jsxs("article",{className:"article-row",children:[e.jsx(U,{size:19}),e.jsxs("div",{children:[e.jsx("h2",{children:M(s.title)}),e.jsx("p",{children:X(s.updatedAt)})]}),e.jsxs("button",{className:"secondary-button","aria-label":`预览稿件:${M(s.title)}`,onClick:()=>void ve(s.id).then(r=>r&&I(r)),children:[e.jsx(me,{size:14}),"预览"]}),e.jsx("button",{className:"icon-button","aria-label":`删除稿件:${M(s.title)}`,disabled:(o==null?void 0:o.status)==="syncing"&&o.draftId===s.id,onClick:()=>z(s),children:e.jsx(je,{size:15})})]},s.id))}),e.jsx("p",{className:"footnote",children:"稿件只保存在这台电脑上。"})]}),_&&t==="tasks"&&(V.length?e.jsx("div",{className:"task-list",children:V.map(ie)}):e.jsxs("div",{className:"empty-state bordered",children:[e.jsx(O,{size:30}),e.jsx("h2",{children:"还没有发布记录"}),e.jsx("p",{children:"让 agent 发布后,这里会显示每个平台的结果。"})]})),_&&t==="accounts"&&e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"section-summary",children:["已登录 ",re," / ",u.length," 个平台",e.jsx("span",{children:k?"正在检查…":""})]}),e.jsx("div",{className:"account-grid",children:u.map(s=>e.jsxs("section",{className:"account-card",children:[e.jsxs("div",{children:[e.jsx("h2",{children:s.name}),e.jsx("span",{className:s.isAuthenticated?"success-text":"",children:s.isAuthenticated?s.username||"已登录":k&&!s.checked?"检查中…":s.error?"暂时查不到":"未登录"})]}),e.jsx("div",{children:e.jsxs("button",{className:"secondary-button","aria-label":`打开${s.name}`,onClick:()=>{R.current=!0,H(s.homepage)},children:[s.isAuthenticated?"打开":"去登录",e.jsx(q,{size:12})]})})]},s.id))}),e.jsx("p",{className:"footnote",children:"登录后回到这里会自动更新。显示“暂时查不到”时,可以打开平台确认后点“重新检查”。"})]}),_&&t==="connection"&&c&&e.jsx(ge,{bridge:c,busy:w,syncing:(o==null?void 0:o.status)==="syncing",update:se,connect:te,updateExtension:ae,copy:ne})]}),L&&e.jsxs("div",{className:"toast",role:"status",children:[e.jsx(Z,{size:16}),e.jsx("span",{children:L}),e.jsx("button",{className:"icon-button","aria-label":"关闭提示",onClick:()=>p(""),children:e.jsx(J,{size:15})})]}),S&&e.jsx(Y,{title:"稿件预览",close:()=>I(null),wide:!0,children:e.jsxs("div",{className:"preview-content",children:[S.cover&&e.jsx("img",{className:"preview-cover",src:S.cover,alt:"文章封面"}),e.jsx("h1",{children:M(S.title)}),e.jsx("div",{className:"article-content",dangerouslySetInnerHTML:{__html:oe.sanitize(Te(S))}})]})}),F&&e.jsx(Y,{title:"删除这篇稿件?",close:()=>{w||z(null)},children:e.jsxs("div",{className:"modal-content",children:[e.jsx("p",{children:"只删除这台电脑上的稿件和记录,已发到平台的内容不受影响。"}),e.jsxs("div",{className:"modal-actions",children:[e.jsx("button",{className:"secondary-button",disabled:w,onClick:()=>z(null),children:"保留稿件"}),e.jsx("button",{className:"danger-button",disabled:w,onClick:async()=>{b(!0);try{await Ne(F.id),z(null),await C()}catch(s){p(s instanceof Error?s.message:"删除失败")}finally{b(!1)}},children:w?"正在删除…":"删除稿件"})]})]})})]})}de(document.getElementById("root")).render(e.jsx(i.StrictMode,{children:e.jsx(_e,{})}));
|