cicy-desktop 2.1.258 → 2.1.259
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/package.json +1 -1
- package/src/backends/sidecar-ipc.js +9 -2
- package/src/sidecar/wsl-docker.js +83 -12
- package/src/tools/index.js +4 -3
- package/src/tools/notify-tools.js +60 -0
package/package.json
CHANGED
|
@@ -52,10 +52,12 @@ const APP_PORT = Number(process.env.CICY_DOCKER_APP_PORT || 8008);
|
|
|
52
52
|
// (含 port)区分。
|
|
53
53
|
const APP_CONTAINER = process.env.CICY_DOCKER_APP_CONTAINER || `cicy-code-docker-${APP_PORT}`;
|
|
54
54
|
const APP_VOLUME = process.env.CICY_DOCKER_APP_VOLUME || `cicy-team-${APP_PORT}`;
|
|
55
|
+
const KOUBO_PORT = 8770;
|
|
55
56
|
|
|
56
57
|
// 用户自定义的额外发布端口(除 :8008 外,给容器内 agent 服务从 Windows 直达用)。
|
|
57
58
|
// 持久化在 userData/docker-ports.json,bootstrap/recreate 都会带上 -p。lazy 取
|
|
58
|
-
// 路径(app 未 ready 时不取)
|
|
59
|
+
// 路径(app 未 ready 时不取)。口播在这个容器内按需启动,因此容器创建时必须
|
|
60
|
+
// 预先发布 :8770;服务未启动时映射保持空闲,不会额外启动 koubo。
|
|
59
61
|
function portsFile() { return path.join(require("electron").app.getPath("userData"), "docker-ports.json"); }
|
|
60
62
|
function sanitizePorts(arr) {
|
|
61
63
|
const out = [], seen = new Set([APP_PORT, 8008]);
|
|
@@ -67,7 +69,12 @@ function sanitizePorts(arr) {
|
|
|
67
69
|
return out;
|
|
68
70
|
}
|
|
69
71
|
function readExtraPorts() {
|
|
70
|
-
try {
|
|
72
|
+
try {
|
|
73
|
+
const j = JSON.parse(fs.readFileSync(portsFile(), "utf8"));
|
|
74
|
+
return sanitizePorts([KOUBO_PORT, ...((j && Array.isArray(j.ports)) ? j.ports : [])]);
|
|
75
|
+
} catch {
|
|
76
|
+
return sanitizePorts([KOUBO_PORT]);
|
|
77
|
+
}
|
|
71
78
|
}
|
|
72
79
|
function writeExtraPorts(ports) {
|
|
73
80
|
try { fs.writeFileSync(portsFile(), JSON.stringify({ ports: sanitizePorts(ports) }, null, 2), "utf8"); } catch (e) { log.warn("[docker-ports] write failed:", e.message); }
|
|
@@ -37,6 +37,32 @@ const ROOTFS_URL = process.env.CICY_WSL_ROOTFS_URL ||
|
|
|
37
37
|
"https://cicy-1372193042-cn.oss-cn-shanghai.aliyuncs.com/rootfs/cicy-wsl-latest.tar.gz";
|
|
38
38
|
|
|
39
39
|
function rootfsPath() { return path.join(docker.downloadsDir(), "cicy-wsl-rootfs.tar.gz"); }
|
|
40
|
+
function shellQuote(value) { return `'${String(value).replace(/'/g, "'\\''")}'`; }
|
|
41
|
+
|
|
42
|
+
// `docker info`/Windows device detection is not enough: the NVIDIA Container
|
|
43
|
+
// Runtime may be missing even when Windows itself has an NVIDIA card. Prove that
|
|
44
|
+
// this Docker Engine can actually expose the GPU to the workload image. Cache
|
|
45
|
+
// briefly so a bootstrap/recreate flow does not launch repeated probe containers.
|
|
46
|
+
const gpuProbeCache = new Map();
|
|
47
|
+
async function dockerGpuAvailable({ image = IMAGE, force = false } = {}) {
|
|
48
|
+
const cacheKey = String(image);
|
|
49
|
+
const cached = gpuProbeCache.get(cacheKey);
|
|
50
|
+
if (!force && cached && Date.now() - cached.ts < 30000) return cached.value;
|
|
51
|
+
|
|
52
|
+
let value = false;
|
|
53
|
+
try {
|
|
54
|
+
await wslRun(
|
|
55
|
+
`docker run --rm --gpus all --entrypoint sh ${shellQuote(image)} -lc ` +
|
|
56
|
+
shellQuote("if command -v nvidia-smi >/dev/null 2>&1; then nvidia-smi -L >/dev/null; else test -c /dev/nvidiactl; fi"),
|
|
57
|
+
{ timeout: 30000 }
|
|
58
|
+
);
|
|
59
|
+
value = true;
|
|
60
|
+
} catch (e) {
|
|
61
|
+
log.info(`[wsl-docker] NVIDIA GPU unavailable for ${cacheKey}: ${String(e.stderr || e.message || "").trim().slice(-300)}`);
|
|
62
|
+
}
|
|
63
|
+
gpuProbeCache.set(cacheKey, { ts: Date.now(), value });
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
40
66
|
// WSL2 kernel update package (the small ~17MB MSI behind aka.ms/wsl2kernel).
|
|
41
67
|
const KERNEL_MSI_URL = process.env.CICY_WSL_KERNEL_URL || "https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi";
|
|
42
68
|
|
|
@@ -251,7 +277,45 @@ function wslTerminate() {
|
|
|
251
277
|
});
|
|
252
278
|
}
|
|
253
279
|
|
|
254
|
-
|
|
280
|
+
function largestFixedDrive() {
|
|
281
|
+
return new Promise((resolve) => {
|
|
282
|
+
const script = [
|
|
283
|
+
"Get-CimInstance Win32_LogicalDisk -Filter 'DriveType=3'",
|
|
284
|
+
"| Where-Object { $_.FreeSpace -gt 0 }",
|
|
285
|
+
"| Sort-Object FreeSpace -Descending",
|
|
286
|
+
"| Select-Object -First 1 -ExpandProperty DeviceID",
|
|
287
|
+
].join(" ");
|
|
288
|
+
execFile(
|
|
289
|
+
"powershell",
|
|
290
|
+
["-NoProfile", "-NonInteractive", "-Command", script],
|
|
291
|
+
{ timeout: 15000, windowsHide: true },
|
|
292
|
+
(err, stdout) => {
|
|
293
|
+
const drive = String(stdout || "").trim().toUpperCase();
|
|
294
|
+
resolve(!err && /^[A-Z]:$/.test(drive) ? drive : "");
|
|
295
|
+
},
|
|
296
|
+
);
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async function resolveWslInstallDir(requested) {
|
|
301
|
+
const fallback = path.join(
|
|
302
|
+
process.env["LOCALAPPDATA"] || path.join(os.homedir(), "AppData", "Local"),
|
|
303
|
+
"cicy-code-wsl",
|
|
304
|
+
);
|
|
305
|
+
const raw = String(requested || process.env.CICY_WSL_INSTALL_DIR || "").trim();
|
|
306
|
+
if (!raw) {
|
|
307
|
+
const drive = await largestFixedDrive();
|
|
308
|
+
return drive ? path.win32.join(`${drive}\\`, "CiCy", "cicy-code-wsl") : fallback;
|
|
309
|
+
}
|
|
310
|
+
// This runs on Windows, but win32.isAbsolute also makes the validation
|
|
311
|
+
// deterministic in CI on other platforms.
|
|
312
|
+
if (!path.win32.isAbsolute(raw)) {
|
|
313
|
+
throw new Error("WSL 安装目录必须是绝对路径,例如 D:\\CiCy\\wsl");
|
|
314
|
+
}
|
|
315
|
+
return path.win32.normalize(raw);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async function installDistro({ emit, installDir: requestedInstallDir } = {}) {
|
|
255
319
|
// 1) Download the PRE-BAKED rootfs (Ubuntu+Docker+image baked in, ~122MB) with
|
|
256
320
|
// a real progress bar. curl is ~10× faster than node's downloader on OSS.
|
|
257
321
|
const dest = rootfsPath();
|
|
@@ -279,7 +343,7 @@ async function installDistro({ emit } = {}) {
|
|
|
279
343
|
// distro to v2 — we do NOT run `--set-default-version` (that would change the
|
|
280
344
|
// user's global default). The WSL2 kernel is shared; we install it ONLY when
|
|
281
345
|
// import actually fails for lack of it (never downgrade an existing kernel).
|
|
282
|
-
const installDir =
|
|
346
|
+
const installDir = await resolveWslInstallDir(requestedInstallDir);
|
|
283
347
|
try { fs.mkdirSync(installDir, { recursive: true }); } catch {}
|
|
284
348
|
emit && emit({ phase: "container", status: "running", message: `$ wsl --import ${DISTRO} "${installDir}" "${dest}" --version 2(1-4 分钟,无实时进度,请耐心)` });
|
|
285
349
|
try {
|
|
@@ -601,7 +665,7 @@ function publishArgs(port, extraPorts = []) {
|
|
|
601
665
|
return args;
|
|
602
666
|
}
|
|
603
667
|
|
|
604
|
-
async function runContainer({ port = 8008, container = "cicy-code-docker", volume = "cicy-team-8008", env = {}, extraPorts = [], dockerSock = false, emit } = {}) {
|
|
668
|
+
async function runContainer({ port = 8008, container = "cicy-code-docker", volume = "cicy-team-8008", env = {}, extraPorts = [], dockerSock = false, gpu = false, gpuProbeImage = IMAGE, emit } = {}) {
|
|
605
669
|
// 每次容器"启动"(含已在跑被 adopt)都确保桌面快捷方式存在 —— 不存在就建,坏了就修。
|
|
606
670
|
if (await probeHealth(port)) { ensureDesktopShortcut(volume, port).catch(() => {}); return { adopted: true }; }
|
|
607
671
|
// Replace any stale same-named container.
|
|
@@ -626,7 +690,14 @@ async function runContainer({ port = 8008, container = "cicy-code-docker", volum
|
|
|
626
690
|
let doodOn = dockerSock;
|
|
627
691
|
if (!doodOn) { try { doodOn = require("./cicy-code").isDood(); } catch {} }
|
|
628
692
|
const sockArg = doodOn ? await dockerSockMountArg() : "";
|
|
629
|
-
const
|
|
693
|
+
const gpuEnabled = (gpu === true || gpu === "auto")
|
|
694
|
+
? await dockerGpuAvailable({ image: gpuProbeImage })
|
|
695
|
+
: false;
|
|
696
|
+
const gpuArg = gpuEnabled ? "--gpus all" : "";
|
|
697
|
+
if (gpu && !gpuEnabled && emit) {
|
|
698
|
+
emit({ phase: "container", status: "running", message: "未检测到可用的 NVIDIA Container Runtime,按 CPU 模式启动" });
|
|
699
|
+
}
|
|
700
|
+
const cmd = `docker run -d --name ${container} --restart unless-stopped ${gpuArg} --dns 223.5.5.5 --dns 8.8.8.8 ${publishArgs(port, extraPorts)} -e CICY_PUBLIC=1 -v ${volume}:/home/cicy ${projectsMountArg()} ${drivesArg} ${sockArg} ${envArgs} ${IMAGE}`;
|
|
630
701
|
emit && emit({ phase: "container", status: "running", message: `$ ${cmd.length > 220 ? cmd.slice(0, 220) + " …" : cmd}` });
|
|
631
702
|
await wslRun(cmd, { timeout: 60000 }); // 失败时 err.stderr 带 docker 真错误 → _bootstrap 的 errTail 显示
|
|
632
703
|
ensureDesktopShortcut(volume, port).catch(() => {});
|
|
@@ -791,7 +862,7 @@ async function bootstrap(opts = {}) {
|
|
|
791
862
|
// 注意: 默认容器/卷名保持 cicy-team / cicy-code-docker(回退实测"现在不行了"的改动)。
|
|
792
863
|
// live 路径(docker:app-bootstrap)始终传显式 APP_*(cicy-team-8008)名,不靠这里的默认;
|
|
793
864
|
// 改默认会让既有 cicy-team 卷的装机对不上 → 退回原值。
|
|
794
|
-
async function _bootstrap({ onProgress, port = 8008, container = "cicy-code-docker", volume = "cicy-team", env = {}, extraPorts = [], dockerSock = false } = {}) {
|
|
865
|
+
async function _bootstrap({ onProgress, port = 8008, container = "cicy-code-docker", volume = "cicy-team", env = {}, extraPorts = [], dockerSock = false, gpu = false, gpuProbeImage = IMAGE, wslInstallDir } = {}) {
|
|
795
866
|
const emit = (ev) => { try { onProgress && onProgress(ev); } catch {} };
|
|
796
867
|
|
|
797
868
|
// Structured, PERSISTED trace of the whole run (electron-log → main.log) so a
|
|
@@ -859,7 +930,7 @@ async function _bootstrap({ onProgress, port = 8008, container = "cicy-code-dock
|
|
|
859
930
|
// 2) Ubuntu distro
|
|
860
931
|
begin("ensure-distro");
|
|
861
932
|
if (!(await distroInstalled())) {
|
|
862
|
-
try { await installDistro({ emit }); } catch (e) { fail("distro_install_failed", e.message); emit({ phase: "install-docker", status: "error", message: `Ubuntu 安装失败:${e.message}(点重试)${errTail(e)}` }); finish(false, "distro_install_failed"); return { ok: false, reason: "distro_install_failed" }; }
|
|
933
|
+
try { await installDistro({ emit, installDir: wslInstallDir }); } catch (e) { fail("distro_install_failed", e.message); emit({ phase: "install-docker", status: "error", message: `Ubuntu 安装失败:${e.message}(点重试)${errTail(e)}` }); finish(false, "distro_install_failed"); return { ok: false, reason: "distro_install_failed" }; }
|
|
863
934
|
const t0 = Date.now();
|
|
864
935
|
const ok = await docker.waitUntil(() => distroInstalled(), { totalMs: 600000, everyMs: 5000, onTick: () => emit({ phase: "install-docker", status: "running", message: `正在下载/注册 Ubuntu…(已 ${Math.round((Date.now() - t0) / 1000)}s,首次较慢请耐心)` }) });
|
|
865
936
|
if (!ok) { fail("distro_not_ready"); emit({ phase: "install-docker", status: "error", message: "Ubuntu 还没装好——稍等或点「重试」" }); finish(false, "distro_not_ready"); return { ok: false, reason: "distro_not_ready" }; }
|
|
@@ -902,7 +973,7 @@ async function _bootstrap({ onProgress, port = 8008, container = "cicy-code-dock
|
|
|
902
973
|
begin("run-container");
|
|
903
974
|
if (!(await probeHealth(port))) {
|
|
904
975
|
emit({ phase: "container", status: "running", message: "启动 cicy-code 服务…" });
|
|
905
|
-
try { await runContainer({ port, container, volume, env, extraPorts, dockerSock, emit }); }
|
|
976
|
+
try { await runContainer({ port, container, volume, env, extraPorts, dockerSock, gpu, gpuProbeImage, emit }); }
|
|
906
977
|
catch (e) { fail("container_start_failed", e.message); emit({ phase: "container", status: "error", message: `服务启动失败:${e.message}(点重试)${errTail(e)}` }); finish(false, "container_start_failed"); return { ok: false, reason: "container_start_failed" }; }
|
|
907
978
|
done();
|
|
908
979
|
} else done(true);
|
|
@@ -1055,14 +1126,14 @@ async function dockerRestart({ container = "cicy-code-docker-8008" } = {}) {
|
|
|
1055
1126
|
// 重建容器:docker rm -f 旧容器 + docker run 新容器(用新 env,如新的 docker team 网关
|
|
1056
1127
|
// key)。**保留 volume**(数据/api_token/deviceId 不丢),只是换掉容器本身 + env。
|
|
1057
1128
|
// 破坏性(短暂中断 + 换 key)→ 调用方要 confirm。
|
|
1058
|
-
async function recreate({ onProgress, port = 8008, container = "cicy-code-docker-8008", volume = "cicy-team-8008", env = {}, extraPorts = [], dockerSock = false } = {}) {
|
|
1129
|
+
async function recreate({ onProgress, port = 8008, container = "cicy-code-docker-8008", volume = "cicy-team-8008", env = {}, extraPorts = [], dockerSock = false, gpu = false, gpuProbeImage = IMAGE } = {}) {
|
|
1059
1130
|
const emit = (ev) => { try { onProgress && onProgress(ev); } catch {} };
|
|
1060
1131
|
// 重建 = 用最新镜像重建。OSS 有更新版先刷新(非破坏性,不删发行版/volume),再 rm + run。
|
|
1061
1132
|
try { await ensureFreshImage({ emit }); } catch (e) { emit({ phase: "image", status: "running", message: `镜像刷新跳过(${e.message}),用现有镜像重建` }); }
|
|
1062
1133
|
// 强删占用该端口的**任何**容器(含老名字 cicy-code-docker)+ 目标容器 —— 否则
|
|
1063
1134
|
// runContainer 开头的 probeHealth 看到旧容器还健康会 adopt 它、不重建,key 就换不了。
|
|
1064
1135
|
try { await wslRun(`docker ps -aq --filter publish=${port} | xargs -r docker rm -f 2>/dev/null; docker rm -f ${container} 2>/dev/null; true`, { timeout: 30000 }); } catch {}
|
|
1065
|
-
const r = await runContainer({ port, container, volume, env, extraPorts, dockerSock, emit });
|
|
1136
|
+
const r = await runContainer({ port, container, volume, env, extraPorts, dockerSock, gpu, gpuProbeImage, emit });
|
|
1066
1137
|
try { await ensureDesktopShortcut(volume, port); } catch {}
|
|
1067
1138
|
// 等 :8008 真正起来再返回——否则 docker run 一返回卡片就探测「没运行」,显示「启动」让
|
|
1068
1139
|
// 用户再点一下(实测:端口保存后重建,drawer 关了还要手动点启动)。
|
|
@@ -1085,13 +1156,13 @@ function unregisterDistro() {
|
|
|
1085
1156
|
// downloader, which copes with the flaky CN DNS that bare curl can't) is the
|
|
1086
1157
|
// only reliable CN update path. This RESETS the distro: the cicy-team volume is
|
|
1087
1158
|
// re-created and the instance re-seeds (new token) on next boot.
|
|
1088
|
-
async function upgrade({ onProgress, port = 8008, container = "cicy-code-docker", volume = "cicy-team", env = {} } = {}) {
|
|
1159
|
+
async function upgrade({ onProgress, port = 8008, container = "cicy-code-docker", volume = "cicy-team", env = {}, extraPorts = [], dockerSock = false, gpu = false, gpuProbeImage = IMAGE, wslInstallDir } = {}) {
|
|
1089
1160
|
const emit = (ev) => { try { onProgress && onProgress(ev); } catch {} };
|
|
1090
1161
|
emit({ phase: "install-docker", status: "running", message: "升级 = 拉取最新运行环境并重装(会重置容器数据)…" });
|
|
1091
1162
|
try { await stop({ container }); } catch {}
|
|
1092
1163
|
try { await unregisterDistro(); } catch {}
|
|
1093
1164
|
// Reuse the robust one-shot install flow (download → import → dockerd → run).
|
|
1094
|
-
return await _bootstrap({ onProgress, port, container, volume, env, dockerSock });
|
|
1165
|
+
return await _bootstrap({ onProgress, port, container, volume, env, extraPorts, dockerSock, gpu, gpuProbeImage, wslInstallDir });
|
|
1095
1166
|
}
|
|
1096
1167
|
|
|
1097
1168
|
// 容器里有没有注入网关 key(reconcile 自愈用):printenv 看 CICY_AI_GATEWAY_LLM_API_KEY。
|
|
@@ -1109,5 +1180,5 @@ async function readMihomoConfig(container = "cicy-code-docker-8008") {
|
|
|
1109
1180
|
module.exports = {
|
|
1110
1181
|
bootstrap, status, restart, stop, dockerRestart, recreate, update, upgrade, runContainer, readContainerToken,
|
|
1111
1182
|
distroInstalled, dockerInstalled, dockerEngineUp, imagePresent, probeHealth, wslRun, hasGatewayKey,
|
|
1112
|
-
readMihomoConfig, repairWsl, lxssWedged,
|
|
1183
|
+
readMihomoConfig, repairWsl, lxssWedged, dockerGpuAvailable, resolveWslInstallDir,
|
|
1113
1184
|
};
|
package/src/tools/index.js
CHANGED
|
@@ -12,9 +12,10 @@ module.exports = [
|
|
|
12
12
|
require("./exec-js"),
|
|
13
13
|
require("./clipboard-tools"),
|
|
14
14
|
require("./exec-tools"),
|
|
15
|
-
require("./file-tools"),
|
|
16
|
-
require("./system-tools"),
|
|
17
|
-
require("./
|
|
15
|
+
require("./file-tools"),
|
|
16
|
+
require("./system-tools"),
|
|
17
|
+
require("./notify-tools"),
|
|
18
|
+
require("./automation-tools"),
|
|
18
19
|
require("./account-tools"),
|
|
19
20
|
require("./device-tools"),
|
|
20
21
|
require("./desktop-snapshot-tools"),
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Copyright 2026 CiCy AI
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
const { Notification, BrowserWindow, shell } = require("electron");
|
|
5
|
+
const { z } = require("zod");
|
|
6
|
+
|
|
7
|
+
// OS desktop notification (mac Notification Center / Windows toast / Linux
|
|
8
|
+
// libnotify). Main-process Electron Notification — no renderer/window needed,
|
|
9
|
+
// so it works even when every window is closed or minimized.
|
|
10
|
+
//
|
|
11
|
+
// Windows: toasts require an AppUserModelId, which main.js already sets
|
|
12
|
+
// ("com.cicy.desktop"). Packaged builds inherit it; dev runs show the toast
|
|
13
|
+
// attributed to Electron until packaged.
|
|
14
|
+
//
|
|
15
|
+
// Click behavior: focus the main CiCy Desktop window (default), then open
|
|
16
|
+
// `url` in the system browser if given. Deep-linking into an in-app tab is
|
|
17
|
+
// deliberately NOT done here — tab routing belongs to tab-browser tools.
|
|
18
|
+
function registerTools(registerTool) {
|
|
19
|
+
registerTool(
|
|
20
|
+
"notify",
|
|
21
|
+
"发送操作系统桌面通知(mac 通知中心 / Windows toast)。点击通知默认聚焦 CiCy Desktop 主窗口;可选 url 在点击时用系统浏览器打开。",
|
|
22
|
+
z.object({
|
|
23
|
+
title: z.string().describe("通知标题(大字)"),
|
|
24
|
+
body: z.string().optional().describe("通知正文(小字,放 prompt/消息摘要)"),
|
|
25
|
+
subtitle: z.string().optional().describe("副标题(仅 macOS 显示)"),
|
|
26
|
+
silent: z.boolean().optional().describe("true = 不播放提示音"),
|
|
27
|
+
url: z.string().optional().describe("点击通知时用系统默认浏览器打开的 URL"),
|
|
28
|
+
focus: z.boolean().optional().describe("点击通知时聚焦主窗口,默认 true"),
|
|
29
|
+
}),
|
|
30
|
+
async ({ title, body, subtitle, silent, url, focus }) => {
|
|
31
|
+
if (!Notification.isSupported()) {
|
|
32
|
+
return {
|
|
33
|
+
content: [{ type: "text", text: JSON.stringify({ ok: false, reason: "notifications not supported on this platform" }) }],
|
|
34
|
+
isError: true,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const n = new Notification({ title, body: body || "", subtitle, silent: !!silent });
|
|
38
|
+
n.on("click", () => {
|
|
39
|
+
try {
|
|
40
|
+
if (focus !== false) {
|
|
41
|
+
const win = BrowserWindow.getAllWindows().find((w) => !w.isDestroyed());
|
|
42
|
+
if (win) {
|
|
43
|
+
if (win.isMinimized()) win.restore();
|
|
44
|
+
win.show();
|
|
45
|
+
win.focus();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (url) shell.openExternal(url);
|
|
49
|
+
} catch {}
|
|
50
|
+
});
|
|
51
|
+
n.show();
|
|
52
|
+
return {
|
|
53
|
+
content: [{ type: "text", text: JSON.stringify({ ok: true, platform: process.platform, title }) }],
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
{ tag: "System" }
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = registerTools;
|