writedock-mcp 0.4.14 → 0.4.16
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 +3 -2
- package/extension/assets/{index.ts-CfQ_yCaZ.js → index.ts-Ch2Bzedq.js} +10 -10
- package/extension/manifest.json +1 -1
- package/extension/service-worker-loader.js +1 -1
- package/extension//345/256/211/350/243/205/344/270/216/346/233/264/346/226/260.txt +1 -1
- package/index.cjs +118 -30
- package/package.json +1 -1
- package/source/package.json +1 -1
- package/source/src/__tests__/launcher.test.ts +100 -0
- package/source/src/__tests__/setup.test.ts +50 -7
- package/source/src/launcher.ts +82 -0
- package/source/src/server.ts +1 -1
- package/source/src/setup.ts +35 -22
package/source/src/setup.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process'
|
|
2
|
-
import { copyFile, mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
2
|
+
import { access, copyFile, mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
3
3
|
import { homedir } from 'node:os'
|
|
4
4
|
import { dirname, join } from 'node:path'
|
|
5
|
+
import { writeLauncher } from './launcher'
|
|
5
6
|
import { bundledExtension, defaultExtensionDirectory, findInstalledExtension, installExtensionFiles } from './extension-update'
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const serverCommand = (platform: NodeJS.Platform) => platform === 'win32'
|
|
10
|
-
? { command: 'cmd', args: ['/c', 'npx', ...NPX_ARGS] }
|
|
11
|
-
: { command: 'npx', args: NPX_ARGS }
|
|
8
|
+
type ServerCommand = { command: string; args: string[] }
|
|
9
|
+
const isOurs = (text: string) => text.includes('writedock-mcp@latest') || text.includes('start-mcp')
|
|
12
10
|
|
|
13
11
|
async function readIfPresent(path: string) {
|
|
14
12
|
// Windows editors may save a BOM, which JSON.parse rejects.
|
|
@@ -22,26 +20,38 @@ async function save(path: string, content: string, previous: string | undefined)
|
|
|
22
20
|
await writeFile(path, content)
|
|
23
21
|
}
|
|
24
22
|
|
|
25
|
-
/**
|
|
26
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Keeps the user's other settings. An entry from an earlier setup moves to the launcher. Anything else stays,
|
|
25
|
+
* because it may hold a custom command, port or data directory.
|
|
26
|
+
*/
|
|
27
|
+
export async function configureCodex(path: string, server: ServerCommand): Promise<'added' | 'updated' | 'present'> {
|
|
27
28
|
const previous = await readIfPresent(path)
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
29
|
+
const lines = `command = ${JSON.stringify(server.command)}\nargs = ${JSON.stringify(server.args)}\nstartup_timeout_sec = 120\n`
|
|
30
|
+
const section = previous === undefined ? null : /^[ \t]*\[mcp_servers\.writedock\][^\n]*\n([\s\S]*?)(?=^[ \t]*\[|(?![\s\S]))/m.exec(previous)
|
|
31
|
+
if (section && previous !== undefined) {
|
|
32
|
+
const customised = /^[ \t]*\[mcp_servers\.writedock\./m.test(previous) || !isOurs(section[1])
|
|
33
|
+
const current = section[1].includes(JSON.stringify(server.args).slice(1, -1)) && section[1].includes(JSON.stringify(server.command))
|
|
34
|
+
if (customised || current) return 'present'
|
|
35
|
+
const start = section.index + section[0].length - section[1].length
|
|
36
|
+
await save(path, `${previous.slice(0, start)}${lines}\n${previous.slice(start + section[1].length)}`, previous)
|
|
37
|
+
return 'updated'
|
|
38
|
+
}
|
|
39
|
+
const entry = `[mcp_servers.writedock]\n${lines}`
|
|
31
40
|
await save(path, previous ? `${previous.replace(/\s*$/, '')}\n\n${entry}` : entry, previous)
|
|
32
41
|
return 'added'
|
|
33
42
|
}
|
|
34
43
|
|
|
35
|
-
export async function configureJsonClient(path: string,
|
|
44
|
+
export async function configureJsonClient(path: string, server: ServerCommand): Promise<'added' | 'updated' | 'present'> {
|
|
36
45
|
const previous = await readIfPresent(path)
|
|
37
|
-
const config = previous?.trim() ? JSON.parse(previous) as { mcpServers?: Record<string, unknown> } : {}
|
|
38
|
-
|
|
39
|
-
|
|
46
|
+
const config = previous?.trim() ? JSON.parse(previous) as { mcpServers?: Record<string, { command?: unknown; args?: unknown; env?: unknown }> } : {}
|
|
47
|
+
const existing = config.mcpServers?.writedock
|
|
48
|
+
if (existing && (existing.env || !isOurs(JSON.stringify(existing.args ?? [])) || JSON.stringify([existing.command, existing.args]) === JSON.stringify([server.command, server.args]))) return 'present'
|
|
49
|
+
config.mcpServers = { ...config.mcpServers, writedock: server }
|
|
40
50
|
await save(path, `${JSON.stringify(config, null, 2)}\n`, previous)
|
|
41
|
-
return 'added'
|
|
51
|
+
return existing ? 'updated' : 'added'
|
|
42
52
|
}
|
|
43
53
|
|
|
44
|
-
const exists =
|
|
54
|
+
const exists = (path: string) => access(path).then(() => true, () => false)
|
|
45
55
|
|
|
46
56
|
/** Best effort: shows the folder so the user can pick it, or drag it onto the extensions page. */
|
|
47
57
|
function revealFolder(path: string) {
|
|
@@ -50,15 +60,18 @@ function revealFolder(path: string) {
|
|
|
50
60
|
}
|
|
51
61
|
|
|
52
62
|
/** Only clients already on this computer are configured, so setup never creates settings for an app the user does not have. */
|
|
53
|
-
export async function runSetup(home = homedir(), log: (line: string) => void = console.log, platform: NodeJS.Platform = process.platform) {
|
|
63
|
+
export async function runSetup(home = homedir(), log: (line: string) => void = console.log, platform: NodeJS.Platform = process.platform, nodePath = process.execPath) {
|
|
64
|
+
const server = await writeLauncher(home, platform, nodePath)
|
|
65
|
+
const entry = { command: server.command, args: server.args }
|
|
54
66
|
const claudeDesktop = platform === 'win32'
|
|
55
67
|
? join(process.env.APPDATA || join(home, 'AppData/Roaming'), 'Claude/claude_desktop_config.json')
|
|
56
68
|
: platform === 'darwin'
|
|
57
69
|
? join(home, 'Library/Application Support/Claude/claude_desktop_config.json')
|
|
58
70
|
: join(home, '.config/Claude/claude_desktop_config.json')
|
|
59
|
-
const clients: { name: string; path: string; detect: string; configure: (path: string,
|
|
71
|
+
const clients: { name: string; path: string; detect: string; configure: (path: string, server: ServerCommand) => Promise<'added' | 'updated' | 'present'> }[] = [
|
|
60
72
|
{ name: 'Codex', path: join(home, '.codex/config.toml'), detect: join(home, '.codex/config.toml'), configure: configureCodex },
|
|
61
73
|
{ name: 'Claude Code', path: join(home, '.claude.json'), detect: join(home, '.claude.json'), configure: configureJsonClient },
|
|
74
|
+
{ name: 'WorkBuddy', path: join(home, '.workbuddy/mcp.json'), detect: join(home, '.workbuddy'), configure: configureJsonClient },
|
|
62
75
|
{ name: 'Cursor', path: join(home, '.cursor/mcp.json'), detect: join(home, '.cursor/mcp.json'), configure: configureJsonClient },
|
|
63
76
|
{ name: 'Claude Desktop', path: claudeDesktop, detect: claudeDesktop, configure: configureJsonClient },
|
|
64
77
|
]
|
|
@@ -67,13 +80,13 @@ export async function runSetup(home = homedir(), log: (line: string) => void = c
|
|
|
67
80
|
if (!await exists(client.detect)) continue
|
|
68
81
|
found++
|
|
69
82
|
try {
|
|
70
|
-
const state = await client.configure(client.path,
|
|
71
|
-
log(state === '
|
|
83
|
+
const state = await client.configure(client.path, entry)
|
|
84
|
+
log(state === 'present' ? `✓ ${client.name}:已有 WriteDock 配置,未改动` : `✓ ${client.name}:已${state === 'added' ? '添加' : '更新'} WriteDock(原配置备份为 .writedock-backup)`)
|
|
72
85
|
} catch (error) { log(`✗ ${client.name}:配置失败(${error instanceof Error ? error.message : String(error)}),原文件未改动`) }
|
|
73
86
|
}
|
|
74
87
|
if (!found) {
|
|
75
88
|
log('没有找到已知的 agent 客户端。请在你的客户端里添加这个 MCP 服务:')
|
|
76
|
-
log(JSON.stringify({ mcpServers: { writedock:
|
|
89
|
+
log(JSON.stringify({ mcpServers: { writedock: entry } }, null, 2))
|
|
77
90
|
}
|
|
78
91
|
// Keep whichever folder the browser already loads, so a repeat setup does not ask for another manual load.
|
|
79
92
|
const bundle = bundledExtension()
|