desktop-pet-app 0.3.3 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +162 -21
- package/out/main/index.js +32 -76
- package/package.json +11 -3
package/bin/cli.js
CHANGED
|
@@ -1,32 +1,173 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const { spawn } = require('node:child_process')
|
|
3
|
+
const { execFileSync, spawn } = require('node:child_process')
|
|
4
4
|
const fs = require('node:fs')
|
|
5
|
+
const os = require('node:os')
|
|
5
6
|
const path = require('node:path')
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
const manifest = require('../package.json')
|
|
9
|
+
|
|
10
|
+
const MAC_APP_PATH = '/Applications/Desktop Pet.app'
|
|
11
|
+
const MAC_BUNDLE_ID = 'com.xinshu.desktoppet'
|
|
12
|
+
const TAKEOVER_TIMEOUT_MS = 5_000
|
|
13
|
+
|
|
14
|
+
function parseVersion(value) {
|
|
15
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(value)
|
|
16
|
+
if (!match) return undefined
|
|
17
|
+
return {
|
|
18
|
+
major: Number(match[1]),
|
|
19
|
+
minor: Number(match[2]),
|
|
20
|
+
patch: Number(match[3]),
|
|
21
|
+
prerelease: match[4]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function compareVersions(a, b) {
|
|
26
|
+
const left = parseVersion(a)
|
|
27
|
+
const right = parseVersion(b)
|
|
28
|
+
if (!left || !right) throw new Error(`无效版本号:${!left ? a : b}`)
|
|
29
|
+
for (const key of ['major', 'minor', 'patch']) {
|
|
30
|
+
if (left[key] !== right[key]) return left[key] > right[key] ? 1 : -1
|
|
31
|
+
}
|
|
32
|
+
if (!left.prerelease && right.prerelease) return 1
|
|
33
|
+
if (left.prerelease && !right.prerelease) return -1
|
|
34
|
+
if (!left.prerelease || !right.prerelease) return 0
|
|
35
|
+
return left.prerelease.localeCompare(right.prerelease, undefined, { numeric: true })
|
|
16
36
|
}
|
|
17
37
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
38
|
+
function selectLaunchTarget(npmVersion, nativeApp, forceNpm = false) {
|
|
39
|
+
if (!forceNpm && nativeApp && compareVersions(nativeApp.version, npmVersion) >= 0) {
|
|
40
|
+
return { kind: 'native', version: nativeApp.version, appPath: nativeApp.appPath }
|
|
41
|
+
}
|
|
42
|
+
return { kind: 'npm', version: npmVersion }
|
|
22
43
|
}
|
|
23
44
|
|
|
24
|
-
|
|
45
|
+
function readPlistValue(plistPath, key) {
|
|
46
|
+
return execFileSync('/usr/libexec/PlistBuddy', ['-c', `Print :${key}`, plistPath], {
|
|
47
|
+
encoding: 'utf8',
|
|
48
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
49
|
+
}).trim()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function installedMacApp() {
|
|
53
|
+
const plistPath = path.join(MAC_APP_PATH, 'Contents', 'Info.plist')
|
|
54
|
+
const executablePath = path.join(MAC_APP_PATH, 'Contents', 'MacOS', 'Desktop Pet')
|
|
55
|
+
try {
|
|
56
|
+
if (!fs.existsSync(executablePath)) return undefined
|
|
57
|
+
if (readPlistValue(plistPath, 'CFBundleIdentifier') !== MAC_BUNDLE_ID) return undefined
|
|
58
|
+
const version = readPlistValue(plistPath, 'CFBundleShortVersionString')
|
|
59
|
+
if (!parseVersion(version)) return undefined
|
|
60
|
+
return { version, appPath: MAC_APP_PATH }
|
|
61
|
+
} catch {
|
|
62
|
+
return undefined
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function runningDesktopPet() {
|
|
67
|
+
const endpointPath = process.env.DESKTOP_PET_PORT_FILE?.trim() || path.join(os.homedir(), '.desktop-pet-port')
|
|
68
|
+
try {
|
|
69
|
+
const endpoint = JSON.parse(fs.readFileSync(endpointPath, 'utf8'))
|
|
70
|
+
if (!Number.isSafeInteger(endpoint.pid) || endpoint.pid < 1) return undefined
|
|
71
|
+
process.kill(endpoint.pid, 0)
|
|
72
|
+
return {
|
|
73
|
+
pid: endpoint.pid,
|
|
74
|
+
version: typeof endpoint.appVersion === 'string' && parseVersion(endpoint.appVersion)
|
|
75
|
+
? endpoint.appVersion
|
|
76
|
+
: undefined
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
return undefined
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function shouldReplaceRunningVersion(targetVersion, runningVersion) {
|
|
84
|
+
return runningVersion === undefined || compareVersions(targetVersion, runningVersion) > 0
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function processIsDesktopPet(pid) {
|
|
88
|
+
if (process.platform === 'win32') return false
|
|
89
|
+
try {
|
|
90
|
+
const command = execFileSync('/bin/ps', ['-p', String(pid), '-o', 'command='], { encoding: 'utf8' })
|
|
91
|
+
return /desktop[- ]pet/i.test(command)
|
|
92
|
+
} catch {
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function processIsRunning(pid) {
|
|
98
|
+
try {
|
|
99
|
+
process.kill(pid, 0)
|
|
100
|
+
return true
|
|
101
|
+
} catch {
|
|
102
|
+
return false
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function stopOlderRuntime(targetVersion) {
|
|
107
|
+
const running = runningDesktopPet()
|
|
108
|
+
if (!running || !shouldReplaceRunningVersion(targetVersion, running.version) || !processIsDesktopPet(running.pid)) return
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
process.kill(running.pid, 'SIGTERM')
|
|
112
|
+
} catch {
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const deadline = Date.now() + TAKEOVER_TIMEOUT_MS
|
|
117
|
+
while (Date.now() < deadline && processIsRunning(running.pid)) {
|
|
118
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100)
|
|
119
|
+
}
|
|
120
|
+
if (processIsRunning(running.pid)) {
|
|
121
|
+
try {
|
|
122
|
+
process.kill(running.pid, 'SIGKILL')
|
|
123
|
+
} catch {}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function npmElectronPath() {
|
|
128
|
+
try {
|
|
129
|
+
return require('electron')
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error('未找到 electron 包,请重新安装 desktop-pet-app。')
|
|
132
|
+
console.error('若在国内网络安装失败,可先设置镜像再重装:')
|
|
133
|
+
console.error(' ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ npm install --global <包路径或包名>')
|
|
134
|
+
console.error(error instanceof Error ? error.message : String(error))
|
|
135
|
+
process.exit(1)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function launch() {
|
|
140
|
+
const appPath = path.join(__dirname, '..')
|
|
141
|
+
const forceNpm = Boolean(process.env.DESKTOP_PET_PROFILE?.trim()) || process.platform !== 'darwin'
|
|
142
|
+
const target = selectLaunchTarget(
|
|
143
|
+
manifest.version,
|
|
144
|
+
process.platform === 'darwin' ? installedMacApp() : undefined,
|
|
145
|
+
forceNpm
|
|
146
|
+
)
|
|
147
|
+
stopOlderRuntime(target.version)
|
|
148
|
+
|
|
149
|
+
let command
|
|
150
|
+
let args
|
|
151
|
+
let env = process.env
|
|
152
|
+
if (target.kind === 'native') {
|
|
153
|
+
command = '/usr/bin/open'
|
|
154
|
+
args = ['-n', target.appPath, '--args', ...process.argv.slice(2)]
|
|
155
|
+
} else {
|
|
156
|
+
command = npmElectronPath()
|
|
157
|
+
if (typeof command !== 'string' || !fs.existsSync(command)) {
|
|
158
|
+
console.error('electron 已安装但二进制缺失,请用镜像重装:')
|
|
159
|
+
console.error(' ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ npm install --global --force <包路径或包名>')
|
|
160
|
+
process.exit(1)
|
|
161
|
+
}
|
|
162
|
+
args = [appPath, ...process.argv.slice(2)]
|
|
163
|
+
env = { ...process.env, DESKTOP_PET_NPM_DISTRIBUTION: '1' }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const child = spawn(command, args, { stdio: 'ignore', detached: true, env })
|
|
167
|
+
child.on('error', (error) => console.error(`Desktop Pet 启动失败:${error.message}`))
|
|
168
|
+
child.unref()
|
|
169
|
+
}
|
|
25
170
|
|
|
26
|
-
|
|
27
|
-
stdio: 'ignore',
|
|
28
|
-
detached: true,
|
|
29
|
-
env: { ...process.env, DESKTOP_PET_NPM_DISTRIBUTION: '1' }
|
|
30
|
-
})
|
|
171
|
+
if (require.main === module) launch()
|
|
31
172
|
|
|
32
|
-
|
|
173
|
+
module.exports = { compareVersions, selectLaunchTarget, shouldReplaceRunningVersion }
|
package/out/main/index.js
CHANGED
|
@@ -22,8 +22,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
22
22
|
mod
|
|
23
23
|
));
|
|
24
24
|
const electron = require("electron");
|
|
25
|
-
const node_path = require("node:path");
|
|
26
25
|
const node_fs = require("node:fs");
|
|
26
|
+
const node_path = require("node:path");
|
|
27
27
|
const path = require("path");
|
|
28
28
|
const os = require("os");
|
|
29
29
|
const node_crypto = require("node:crypto");
|
|
@@ -1680,6 +1680,7 @@ const REQUEST_TIMEOUT_MS = 6e3;
|
|
|
1680
1680
|
const DOWNLOAD_TIMEOUT_MS = 10 * 6e4;
|
|
1681
1681
|
const LEGACY_OFFICIAL_HTTP_HOST = "47.94.20.104";
|
|
1682
1682
|
const MAC_BUNDLE_ID = "com.xinshu.desktoppet";
|
|
1683
|
+
const MAC_INSTALLED_APP = "/Applications/Desktop Pet.app";
|
|
1683
1684
|
let status = { kind: "idle" };
|
|
1684
1685
|
function updateCheckURL() {
|
|
1685
1686
|
const override = process.env.DESKTOP_PET_UPDATE_API?.trim();
|
|
@@ -1915,22 +1916,17 @@ const restartFallback = () => {
|
|
|
1915
1916
|
child.unref()
|
|
1916
1917
|
}
|
|
1917
1918
|
const findInstalledMacApp = async () => {
|
|
1918
|
-
const
|
|
1919
|
-
const
|
|
1920
|
-
const
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
const plist = join(candidate, 'Contents', 'Info.plist')
|
|
1924
|
-
const result = await run('/usr/libexec/PlistBuddy', ['-c', 'Print :CFBundleShortVersionString', plist])
|
|
1925
|
-
if (result.stdout.trim() === targetVersion) return candidate
|
|
1926
|
-
} catch {}
|
|
1919
|
+
const plist = join('${MAC_INSTALLED_APP}', 'Contents', 'Info.plist')
|
|
1920
|
+
const bundle = await run('/usr/libexec/PlistBuddy', ['-c', 'Print :CFBundleIdentifier', plist])
|
|
1921
|
+
const version = await run('/usr/libexec/PlistBuddy', ['-c', 'Print :CFBundleShortVersionString', plist])
|
|
1922
|
+
if (bundle.stdout.trim() !== '${MAC_BUNDLE_ID}' || version.stdout.trim() !== targetVersion) {
|
|
1923
|
+
throw new Error('installed app identity or version does not match the verified update')
|
|
1927
1924
|
}
|
|
1928
|
-
|
|
1925
|
+
return '${MAC_INSTALLED_APP}'
|
|
1929
1926
|
}
|
|
1930
1927
|
const restartInstalledApp = async () => {
|
|
1931
1928
|
if (platform === 'darwin') {
|
|
1932
|
-
//
|
|
1933
|
-
// 找到版本完全匹配的具体路径再启动,避免按 bundle ID 命中旧副本后静默失败。
|
|
1929
|
+
// PKG 禁止重定位;安装后再次核对固定路径的 bundle ID 与版本再启动。
|
|
1934
1930
|
const installedApp = await findInstalledMacApp()
|
|
1935
1931
|
await run('/usr/bin/open', ['-n', installedApp])
|
|
1936
1932
|
return
|
|
@@ -2653,7 +2649,7 @@ async function readBody(req, maxBytes) {
|
|
|
2653
2649
|
});
|
|
2654
2650
|
}
|
|
2655
2651
|
let eventServer;
|
|
2656
|
-
function startEventServer() {
|
|
2652
|
+
function startEventServer(appVersion = "0.0.0") {
|
|
2657
2653
|
if (eventServer) return;
|
|
2658
2654
|
const sessionToken = crypto.randomBytes(32).toString("base64url");
|
|
2659
2655
|
const server = http.createServer((req, res) => {
|
|
@@ -2710,6 +2706,7 @@ function startEventServer() {
|
|
|
2710
2706
|
if (addr && typeof addr === "object") {
|
|
2711
2707
|
const endpoint = {
|
|
2712
2708
|
protocolVersion: LOCAL_PET_PROTOCOL_VERSION,
|
|
2709
|
+
appVersion,
|
|
2713
2710
|
port: addr.port,
|
|
2714
2711
|
pid: process.pid,
|
|
2715
2712
|
sessionToken,
|
|
@@ -3144,64 +3141,29 @@ async function dutyLoop(adapter) {
|
|
|
3144
3141
|
}
|
|
3145
3142
|
}
|
|
3146
3143
|
}
|
|
3147
|
-
const
|
|
3148
|
-
const
|
|
3149
|
-
function
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
logger.warn("single-instance lock held by an unrelated process, refusing to take over", { pid });
|
|
3155
|
-
return false;
|
|
3156
|
-
}
|
|
3157
|
-
logger.info("taking over running instance", { pid });
|
|
3158
|
-
try {
|
|
3159
|
-
process.kill(pid, "SIGTERM");
|
|
3160
|
-
} catch {
|
|
3161
|
-
return true;
|
|
3162
|
-
}
|
|
3163
|
-
if (waitForExit(pid, TAKEOVER_TIMEOUT_MS)) return true;
|
|
3164
|
-
logger.warn("instance did not exit after SIGTERM, forcing", { pid });
|
|
3165
|
-
try {
|
|
3166
|
-
process.kill(pid, "SIGKILL");
|
|
3167
|
-
} catch {
|
|
3168
|
-
return true;
|
|
3169
|
-
}
|
|
3170
|
-
return waitForExit(pid, 1e3);
|
|
3171
|
-
}
|
|
3172
|
-
function readLockOwnerPid() {
|
|
3173
|
-
let target;
|
|
3174
|
-
try {
|
|
3175
|
-
target = node_fs.readlinkSync(node_path.join(electron.app.getPath("userData"), "SingletonLock"));
|
|
3176
|
-
} catch {
|
|
3177
|
-
return void 0;
|
|
3178
|
-
}
|
|
3179
|
-
const hostPrefix = `${node_os.hostname()}-`;
|
|
3180
|
-
if (!target.startsWith(hostPrefix)) return void 0;
|
|
3181
|
-
const pid = Number(target.slice(hostPrefix.length));
|
|
3182
|
-
return Number.isSafeInteger(pid) && pid > 0 ? pid : void 0;
|
|
3183
|
-
}
|
|
3184
|
-
function processIsDesktopPet(pid) {
|
|
3185
|
-
try {
|
|
3186
|
-
const command = node_child_process.execFileSync("/bin/ps", ["-p", String(pid), "-o", "command="], { encoding: "utf8" });
|
|
3187
|
-
return /desktop[- ]pet/i.test(command);
|
|
3188
|
-
} catch {
|
|
3189
|
-
return false;
|
|
3144
|
+
const PRODUCT_DATA_DIRECTORY = "Desktop Pet";
|
|
3145
|
+
const LEGACY_NPM_DATA_DIRECTORY = "desktop-pet-app";
|
|
3146
|
+
function configureRuntimeIdentity() {
|
|
3147
|
+
const profileDirectory = process.env.DESKTOP_PET_PROFILE?.trim();
|
|
3148
|
+
if (profileDirectory) {
|
|
3149
|
+
electron.app.setPath("userData", node_path.resolve(profileDirectory));
|
|
3150
|
+
return;
|
|
3190
3151
|
}
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
const
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, TAKEOVER_POLL_MS);
|
|
3152
|
+
if (!electron.app.isPackaged && process.env.DESKTOP_PET_NPM_DISTRIBUTION !== "1") return;
|
|
3153
|
+
const appDataDirectory = electron.app.getPath("appData");
|
|
3154
|
+
const canonicalDirectory = node_path.join(appDataDirectory, PRODUCT_DATA_DIRECTORY);
|
|
3155
|
+
if (process.env.DESKTOP_PET_NPM_DISTRIBUTION === "1") {
|
|
3156
|
+
migrateLegacyNpmData(node_path.join(appDataDirectory, LEGACY_NPM_DATA_DIRECTORY), canonicalDirectory);
|
|
3197
3157
|
}
|
|
3198
|
-
|
|
3158
|
+
electron.app.setPath("userData", canonicalDirectory);
|
|
3199
3159
|
}
|
|
3200
|
-
function
|
|
3160
|
+
function migrateLegacyNpmData(legacyDirectory, canonicalDirectory) {
|
|
3161
|
+
if (!node_fs.existsSync(legacyDirectory) || node_fs.existsSync(canonicalDirectory)) return false;
|
|
3201
3162
|
try {
|
|
3202
|
-
|
|
3163
|
+
node_fs.renameSync(legacyDirectory, canonicalDirectory);
|
|
3203
3164
|
return true;
|
|
3204
|
-
} catch {
|
|
3165
|
+
} catch (error) {
|
|
3166
|
+
console.warn(`无法迁移 Desktop Pet 用户数据:${String(error)}`);
|
|
3205
3167
|
return false;
|
|
3206
3168
|
}
|
|
3207
3169
|
}
|
|
@@ -3238,15 +3200,9 @@ function createPetTemplateImage() {
|
|
|
3238
3200
|
image.setTemplateImage(true);
|
|
3239
3201
|
return image;
|
|
3240
3202
|
}
|
|
3241
|
-
|
|
3242
|
-
if (profileDirectory) {
|
|
3243
|
-
electron.app.setPath("userData", node_path.resolve(profileDirectory));
|
|
3244
|
-
}
|
|
3203
|
+
configureRuntimeIdentity();
|
|
3245
3204
|
registerAccountProtocol();
|
|
3246
|
-
|
|
3247
|
-
if (!singleInstance && takeOverRunningInstance()) {
|
|
3248
|
-
singleInstance = electron.app.requestSingleInstanceLock();
|
|
3249
|
-
}
|
|
3205
|
+
const singleInstance = electron.app.requestSingleInstanceLock();
|
|
3250
3206
|
if (!singleInstance) {
|
|
3251
3207
|
logger.warn("another instance is running, quitting");
|
|
3252
3208
|
electron.app.quit();
|
|
@@ -3267,7 +3223,7 @@ function ensurePrimaryPet() {
|
|
|
3267
3223
|
electron.app.whenReady().then(async () => {
|
|
3268
3224
|
createMenuBar();
|
|
3269
3225
|
await startAccountCallbackRouter();
|
|
3270
|
-
startEventServer();
|
|
3226
|
+
startEventServer(electron.app.getVersion());
|
|
3271
3227
|
const activated = await activateStoredDevice().catch((err) => {
|
|
3272
3228
|
logger.warn("device authentication failed", { error: String(err) });
|
|
3273
3229
|
return false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "desktop-pet-app",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "AI desktop pet with MCP support and a self-hosted relay server",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"e2e:codebuddy:context": "node scripts/e2e-codebuddy-context-round.mjs",
|
|
33
33
|
"typecheck": "tsc --noEmit",
|
|
34
34
|
"test": "vitest run",
|
|
35
|
-
"package:mac": "npm run build &&
|
|
35
|
+
"package:mac": "npm run build && electron-builder --mac pkg --publish never",
|
|
36
36
|
"pack:check": "npm pack --dry-run",
|
|
37
37
|
"prepack": "npm run build",
|
|
38
38
|
"prepublishOnly": "npm run typecheck && npm test && npm run pack:check"
|
|
@@ -48,6 +48,11 @@
|
|
|
48
48
|
"bin/**",
|
|
49
49
|
"package.json"
|
|
50
50
|
],
|
|
51
|
+
"pkg": {
|
|
52
|
+
"installLocation": "/Applications",
|
|
53
|
+
"isRelocatable": false,
|
|
54
|
+
"isVersionChecked": true
|
|
55
|
+
},
|
|
51
56
|
"mac": {
|
|
52
57
|
"category": "public.app-category.utilities",
|
|
53
58
|
"target": [
|
|
@@ -58,14 +63,17 @@
|
|
|
58
63
|
"devDependencies": {
|
|
59
64
|
"@types/node": "^22.0.0",
|
|
60
65
|
"@types/ws": "^8.18.1",
|
|
66
|
+
"electron": "^33.2.0",
|
|
61
67
|
"electron-builder": "^26.15.3",
|
|
62
68
|
"electron-vite": "^2.3.0",
|
|
63
69
|
"typescript": "^5.6.3",
|
|
64
70
|
"vite": "^5.4.8",
|
|
65
71
|
"vitest": "^3.2.7"
|
|
66
72
|
},
|
|
73
|
+
"peerDependencies": {
|
|
74
|
+
"electron": "^33.2.0"
|
|
75
|
+
},
|
|
67
76
|
"dependencies": {
|
|
68
|
-
"electron": "^33.2.0",
|
|
69
77
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
70
78
|
"ws": "^8.21.3",
|
|
71
79
|
"zod": "^4.5.4"
|