writedock-mcp 0.4.6 → 0.4.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 +6 -3
- package/extension/assets/editor-DSj8NlXD.js +76 -0
- package/extension/assets/index.ts-CrE-nH9x.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 +30 -86
- 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 +12 -59
- package/source/src/identity.ts +2 -2
- package/source/src/server.ts +3 -7
- package/source/src/setup.ts +22 -12
- package/source/tsup.config.ts +2 -0
- package/extension/assets/editor-DWoK50xk.js +0 -76
- package/extension/assets/index.ts-DbQynGAJ.js +0 -42
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 he}from"./purify.es-1yEQDPgc.js";import{r as i,j as e,c as ue}from"./react-vendor-BrXlnJmF.js";import{S as H,s as V,e as xe}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 pe={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 me=t=>t.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase().trim(),j=(t,o)=>{const l=i.forwardRef(({color:E="currentColor",size:d=24,strokeWidth:N=2,absoluteStrokeWidth:m,className:g="",children:x,...C},c)=>i.createElement("svg",{ref:c,...pe,width:d,height:d,stroke:E,strokeWidth:m?Number(N)*24/Number(d):N,className:["lucide",`lucide-${me(t)}`,g].join(" "),...C},[...o.map(([f,u])=>i.createElement(f,u)),...Array.isArray(x)?x:[x]]));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 Q=j("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 O=j("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=j("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 je=j("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=j("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 G=j("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=j("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 W=j("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 K=j("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 Y=j("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 ye=j("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 ve=j("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 ee=j("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(H.draftIndex))[H.draftIndex]||[]}async function Ne(t){const o=`writedockDraft:${t}`;return(await chrome.storage.local.get(o))[o]||null}async function ge(t){return T("DELETE_DRAFT",{draftId:t})}async function T(t,o={}){const l=await chrome.runtime.sendMessage({type:t,...o});if(l!=null&&l.error)throw new Error(l.error);if(!l||!("data"in l))throw new Error("扩展后台未响应,请在扩展管理页重新加载 WriteDock");return l.data}function we({bridge:t,busy:o,syncing:l,update:E,pair:d,connect:N,updateExtension:m,copy:g}){const[x,C]=i.useState(String(t.port)),[c,f]=i.useState(t.serverPath),[u,b]=i.useState("");i.useEffect(()=>C(String(t.port)),[t.port]),i.useEffect(()=>f(t.serverPath),[t.serverPath]);const w="帮我安装 WriteDock:运行 npx -y writedock-mcp@latest setup,然后告诉我怎么重启你。",y={WRITEDOCK_PORT:String(t.port)},S=()=>JSON.stringify({mcpServers:{writedock:{command:"node",args:[t.serverPath],env:y}}},null,2),I=[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(Q,{size:16})," 已连接"]}),e.jsxs("p",{children:["告诉你的 agent 要写什么、发到哪些平台就行。",I&&` ${I}`]})]})}),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:o||l,onClick:()=>void N(),children:o?"正在连接…":"连接"})})]}):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:w}),e.jsx("div",{className:"settings-actions",children:e.jsxs("button",{className:"primary-button",onClick:()=>void g(w),children:[e.jsx(O,{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:o||l,onClick:()=>void m(),children:o?"正在更新…":"更新扩展"})}),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:o||l,onClick:()=>E({enabled:!t.enabled}),children:e.jsx("span",{})})]}),e.jsxs("form",{onSubmit:v=>{v.preventDefault(),!o&&!l&&u.trim()&&d(u).then(h=>{h&&b("")})},children:[e.jsxs("label",{children:["一次性配对码(仅旧版服务需要)",e.jsx("input",{"aria-label":"一次性配对码",value:u,onChange:v=>b(v.target.value),autoComplete:"off",spellCheck:!1,placeholder:"WD1-9647-XXXX-XXXX-XXXX-XXXX",disabled:o||l})]}),e.jsx("div",{className:"settings-actions",children:e.jsx("button",{className:"secondary-button",type:"submit",disabled:o||l||!u.trim(),children:o?"正在配对…":"配对并连接"})})]}),e.jsxs("div",{className:"settings-grid",children:[e.jsxs("label",{children:["连接端口",e.jsx("input",{"aria-label":"连接端口",inputMode:"numeric",value:x,onChange:v=>C(v.target.value)})]}),e.jsxs("label",{children:["当前浏览器配对密钥",e.jsxs("div",{className:"input-action",children:[e.jsx("input",{"aria-label":"连接密钥",type:"password",readOnly:!0,value:t.token}),e.jsx("button",{className:"icon-button","aria-label":"复制连接密钥",onClick:()=>g(t.token),children:e.jsx(O,{size:14})})]})]})]}),e.jsx("div",{className:"settings-actions",children:e.jsx("button",{className:"secondary-button",disabled:o||l||x===String(t.port),onClick:()=>E({port:Number(x)}),children:"保存端口"})}),e.jsxs("label",{className:"path-setting",children:["本机开发服务路径",e.jsx("input",{"aria-label":"本机开发服务路径",placeholder:"已构建的 packages/mcp-server/dist/index.cjs 路径",value:c,onChange:v=>f(v.target.value)})]}),e.jsxs("div",{className:"settings-actions",children:[e.jsx("button",{className:"secondary-button",disabled:o||c===t.serverPath,onClick:()=>E({serverPath:c}),children:"保存开发路径"}),e.jsx("button",{className:"secondary-button",disabled:!t.serverPath||c!==t.serverPath,onClick:()=>g(S()),children:"复制本机开发配置"})]})]})]})}const Z=t=>new Intl.DateTimeFormat("zh-CN",{month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",hour12:!1}).format(t),L=t=>t.trim()||"未命名稿件",be={syncing:"正在保存",completed:"已保存草稿",partial:"部分成功",failed:"失败",interrupted:"中断了",unknown:"请去平台确认",needs_user_action:"需要你处理",rehearsed:"演练完成"},se={syncing:"正在发布",completed:"已发布",partial:"部分成功",failed:"发布失败",interrupted:"中断了",unknown:"请去平台确认",needs_user_action:"需要你处理",rehearsed:"演练完成"},Ee={...se,syncing:"正在演练",completed:"演练完成",partial:"部分通过",failed:"演练失败"},Ce={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((o,l)=>o.replace(l.marker,`视频:${xe(l.asset.name)}`),t.html),Se=t=>t.publicationState==="rehearsed"?"演练通过,未发布":t.errorCode==="not_logged_in"?"未登录,已跳过":t.publicationState==="needs_user_action"?"需要你处理":t.publicationState==="unknown"?"请去平台确认":t.success?t.draftOnly===!1?"已发布":"已保存草稿":"失败";function J({title:t,children:o,close:l,wide:E=!1}){const d=i.useRef(null),N=i.useRef(l);return N.current=l,i.useEffect(()=>{var x,C;const m=document.activeElement;(C=(x=d.current)==null?void 0:x.querySelector("button"))==null||C.focus();const g=c=>{var w;if(c.key==="Escape"){c.preventDefault(),N.current();return}if(c.key!=="Tab")return;const f=Array.from(((w=d.current)==null?void 0:w.querySelectorAll('button:not(:disabled), a[href], input, [tabindex="0"]'))||[]),u=f[0],b=f.at(-1);c.shiftKey&&document.activeElement===u?(c.preventDefault(),b==null||b.focus()):!c.shiftKey&&document.activeElement===b&&(c.preventDefault(),u==null||u.focus())};return document.addEventListener("keydown",g),()=>{document.removeEventListener("keydown",g),m==null||m.focus()}},[]),e.jsx("div",{className:"modal-backdrop",onMouseDown:m=>{m.target===m.currentTarget&&l()},children:e.jsxs("section",{className:`modal ${E?"wide":""}`,ref:d,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(ee,{size:18})})]}),o]})})}function _e(){const[t,o]=i.useState("tasks"),[l,E]=i.useState([]),[d,N]=i.useState(null),[m,g]=i.useState([]),[x,C]=i.useState([]),[c,f]=i.useState(null),[u,b]=i.useState(!1),[w,y]=i.useState(!1),[S,I]=i.useState(!1),[v,h]=i.useState(""),[D,P]=i.useState(null),[X,z]=i.useState(null),R=i.useRef(!1),_=i.useCallback(async()=>{const[s,r,n]=await Promise.all([ke(),T("GET_SYNC_STATE"),T("GET_SYNC_HISTORY")]);E(s),N(r),g(n)},[]),A=i.useCallback(async()=>{const s=await T("GET_BRIDGE_STATE");return f(s),s},[]),$=i.useCallback(async(s=!1)=>{b(!0);try{C(await T("CHECK_ALL_AUTH",{forceRefresh:s}))}catch(r){h(r instanceof Error?r.message:"登录检测失败")}finally{b(!1)}},[]);i.useEffect(()=>{let s=!1;Promise.all([_(),A()]).then(([,a])=>{s||(a.connected||o("connection"),I(!0),$())}).catch(a=>h(String(a.message||a)));const r=a=>(a.type==="TASK_UPDATED"&&a.task&&(N(a.task),a.task.status!=="syncing"&&_().catch(()=>{})),["DRAFT_SAVED","DRAFT_DELETED"].includes(a.type)&&(a.type==="DRAFT_DELETED"&&(P(p=>(p==null?void 0:p.id)===a.draftId?null:p),N(p=>(p==null?void 0:p.draftId)===a.draftId?null:p),g(p=>p.filter(k=>k.draftId!==a.draftId))),_().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)}},[_,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(!v)return;const s=setTimeout(()=>h(""),7e3);return()=>clearTimeout(s)},[v]);const te=async s=>{y(!0);try{f(await T("SET_BRIDGE_CONFIG",{payload:s}))}catch(r){h(r instanceof Error?r.message:"连接设置保存失败")}finally{y(!1)}},ae=async()=>{y(!0);try{return f(await T("CONNECT_BRIDGE")),!0}catch(s){return h(s instanceof Error?s.message:"连接失败"),!1}finally{y(!1)}},ne=async()=>{y(!0);try{await T("UPDATE_EXTENSION"),h("更新完成,正在重新加载")}catch(s){h(s instanceof Error?s.message:"更新失败")}finally{y(!1)}},ce=async s=>{y(!0);try{return f(await T("PAIR_BRIDGE",{code:s})),h("正在连接"),!0}catch(r){return h(r instanceof Error?r.message:"配对失败"),!1}finally{y(!1)}},F=(s,r=!1)=>{const n=V(s);n&&(async()=>{if(r){const a=new URL(n),k=(await chrome.tabs.query({})).find(de=>{try{const M=new URL(de.url||"");return M.origin===a.origin&&M.pathname===a.pathname&&(a.searchParams.has("article_id")?M.searchParams.get("article_id")===a.searchParams.get("article_id"):a.hash?M.hash===a.hash:M.href===a.href)}catch{return!1}});if(k!=null&&k.id){await chrome.tabs.update(k.id,{active:!0}),await chrome.windows.update(k.windowId,{focused:!0});return}}await chrome.tabs.create({url:n})})().catch(a=>h(a instanceof Error?a.message:"无法打开稿件页"))},re=async s=>{try{await navigator.clipboard.writeText(s),h("已复制")}catch{h("复制失败,请选中文字后复制")}},B=d?[d,...m.filter(s=>s.id!==d.id)]:m,ie=s=>{var r;return((r=x.find(n=>n.id===s))==null?void 0:r.name)||s},le=x.filter(s=>s.isAuthenticated).length,oe=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:L(s.title)}),e.jsx("span",{children:Z(s.startedAt)})]}),e.jsxs("span",{className:`task-status ${s.status}`,children:[s.status==="syncing"&&e.jsx(K,{size:13,className:"spin"}),(s.action==="rehearsal"?Ee:s.action==="publish"?se:be)[s.status]]})]}),e.jsx("div",{className:"task-result-list",children:s.platforms.map(r=>{var p;const n=s.results.find(k=>k.platform===r),a=s.progress[r];return e.jsxs("div",{className:"task-result",children:[e.jsxs("div",{children:[e.jsx("strong",{children:ie(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?Se(n):Ce[(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)&&V(((p=n.userAction)==null?void 0:p.url)||n.postUrl)&&e.jsxs("button",{className:"link-button",onClick:()=>{var k;return F(((k=n.userAction)==null?void 0:k.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:()=>o("connection"),children:[e.jsx("span",{}),c!=null&&c.connected?"已连接":"未连接"]})]}),e.jsxs("aside",{className:"navigation",children:[e.jsx("nav",{"aria-label":"控制台导航",children:[["tasks",G,"发布记录"],["accounts",ve,"平台登录"],["articles",U,"稿件"],["connection",W,"连接 agent"]].map(([s,r,n])=>e.jsxs("button",{className:t===s?"selected":"",onClick:()=>o(s),children:[e.jsx(r,{size:17}),n,s==="tasks"&&(d==null?void 0:d.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 _().catch(s=>h(s.message)),children:[e.jsx(Y,{size:14}),"刷新"]}),t==="accounts"&&e.jsxs("button",{className:"secondary-button",disabled:u,onClick:()=>$(!0),children:[e.jsx(Y,{size:14,className:u?"spin":""}),"重新检查"]})]}),!S&&e.jsxs("div",{className:"empty-state",children:[e.jsx(K,{className:"spin",size:24}),e.jsx("p",{children:"正在加载…"})]}),S&&t==="articles"&&e.jsxs(e.Fragment,{children:[!(c!=null&&c.connected)&&e.jsxs("div",{className:"connection-banner",children:[e.jsx(W,{size:17}),e.jsx("span",{children:c!=null&&c.mcpFound?"发现本机的 WriteDock 服务,点一下就能连接。":"还没有连接到你的 agent。"}),e.jsx("button",{className:"link-button",onClick:()=>o("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:L(s.title)}),e.jsx("p",{children:Z(s.updatedAt)})]}),e.jsxs("button",{className:"secondary-button","aria-label":`预览稿件:${L(s.title)}`,onClick:()=>void Ne(s.id).then(r=>r&&P(r)),children:[e.jsx(je,{size:14}),"预览"]}),e.jsx("button",{className:"icon-button","aria-label":`删除稿件:${L(s.title)}`,disabled:(d==null?void 0:d.status)==="syncing"&&d.draftId===s.id,onClick:()=>z(s),children:e.jsx(ye,{size:15})})]},s.id))}),e.jsx("p",{className:"footnote",children:"稿件只保存在这台电脑上。"})]}),S&&t==="tasks"&&(B.length?e.jsx("div",{className:"task-list",children:B.map(oe)}):e.jsxs("div",{className:"empty-state bordered",children:[e.jsx(G,{size:30}),e.jsx("h2",{children:"还没有发布记录"}),e.jsx("p",{children:"让 agent 发布后,这里会显示每个平台的结果。"})]})),S&&t==="accounts"&&e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"section-summary",children:["已登录 ",le," / ",x.length," 个平台",e.jsx("span",{children:u?"正在检查…":""})]}),e.jsx("div",{className:"account-grid",children:x.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||"已登录":u&&!s.checked?"检查中…":s.error?"暂时查不到":"未登录"})]}),e.jsx("div",{children:e.jsxs("button",{className:"secondary-button","aria-label":`打开${s.name}`,onClick:()=>{R.current=!0,F(s.homepage)},children:[s.isAuthenticated?"打开":"去登录",e.jsx(q,{size:12})]})})]},s.id))}),e.jsx("p",{className:"footnote",children:"登录后回到这里会自动更新。显示“暂时查不到”时,可以打开平台确认后点“重新检查”。"})]}),S&&t==="connection"&&c&&e.jsx(we,{bridge:c,busy:w,syncing:(d==null?void 0:d.status)==="syncing",update:te,pair:ce,connect:ae,updateExtension:ne,copy:re})]}),v&&e.jsxs("div",{className:"toast",role:"status",children:[e.jsx(Q,{size:16}),e.jsx("span",{children:v}),e.jsx("button",{className:"icon-button","aria-label":"关闭提示",onClick:()=>h(""),children:e.jsx(ee,{size:15})})]}),D&&e.jsx(J,{title:"稿件预览",close:()=>P(null),wide:!0,children:e.jsxs("div",{className:"preview-content",children:[D.cover&&e.jsx("img",{className:"preview-cover",src:D.cover,alt:"文章封面"}),e.jsx("h1",{children:L(D.title)}),e.jsx("div",{className:"article-content",dangerouslySetInnerHTML:{__html:he.sanitize(Te(D))}})]})}),X&&e.jsx(J,{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()=>{y(!0);try{await ge(X.id),z(null),await _()}catch(s){h(s instanceof Error?s.message:"删除失败")}finally{y(!1)}},children:w?"正在删除…":"删除稿件"})]})]})})]})}ue(document.getElementById("root")).render(e.jsx(i.StrictMode,{children:e.jsx(_e,{})}));
|