@rethinkingstudio/clawpilot 2.0.0-beta.0 → 2.0.0
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/dist/commands/pair.js +3 -5
- package/dist/commands/pair.js.map +1 -1
- package/dist/platform/service-manager.js +9 -13
- package/dist/platform/service-manager.js.map +1 -1
- package/package.json +7 -1
- package/rethinkingstudio-clawpilot-1.1.17.tgz +0 -0
- package/src/commands/assistant-send.ts +0 -91
- package/src/commands/install.ts +0 -131
- package/src/commands/local-handlers.ts +0 -533
- package/src/commands/openclaw-cli.ts +0 -354
- package/src/commands/pair.ts +0 -128
- package/src/commands/provider-config.ts +0 -275
- package/src/commands/provider-handlers.ts +0 -184
- package/src/commands/provider-registry.ts +0 -138
- package/src/commands/run.ts +0 -34
- package/src/commands/send.ts +0 -42
- package/src/commands/session-key.ts +0 -77
- package/src/commands/set-token.ts +0 -45
- package/src/commands/status.ts +0 -157
- package/src/commands/upload.ts +0 -141
- package/src/config/config.ts +0 -137
- package/src/generated/build-config.ts +0 -1
- package/src/i18n/index.ts +0 -185
- package/src/index.ts +0 -166
- package/src/media/assistant-attachments.ts +0 -205
- package/src/media/oss-uploader.ts +0 -306
- package/src/platform/service-manager.ts +0 -919
- package/src/relay/gateway-client.ts +0 -359
- package/src/relay/reconnect.ts +0 -37
- package/src/relay/relay-manager.ts +0 -328
- package/test-chat.mjs +0 -64
- package/test-direct.mjs +0 -171
- package/tsconfig.json +0 -16
|
@@ -1,919 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, openSync, readFileSync, realpathSync, unlinkSync, writeFileSync } from "fs";
|
|
2
|
-
import { execSync, spawn } from "child_process";
|
|
3
|
-
import { homedir, userInfo } from "os";
|
|
4
|
-
import { dirname, join, resolve } from "path";
|
|
5
|
-
import { createRequire } from "module";
|
|
6
|
-
import { fileURLToPath } from "url";
|
|
7
|
-
|
|
8
|
-
export type ServicePlatform = "macos" | "linux" | "windows" | "unsupported";
|
|
9
|
-
|
|
10
|
-
export interface ServiceStatus {
|
|
11
|
-
platform: ServicePlatform;
|
|
12
|
-
installed: boolean;
|
|
13
|
-
running: boolean;
|
|
14
|
-
serviceName: string;
|
|
15
|
-
manager: string;
|
|
16
|
-
servicePath?: string;
|
|
17
|
-
logPath: string;
|
|
18
|
-
startHint?: string;
|
|
19
|
-
autoStartHint?: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const MAC_LABEL = "com.rethinkingstudio.clawpilot";
|
|
23
|
-
const MAC_LABEL_OLD = "com.rethinkingstudio.clawai";
|
|
24
|
-
const MAC_PLIST_DIR = join(homedir(), "Library", "LaunchAgents");
|
|
25
|
-
const MAC_PLIST_PATH = join(MAC_PLIST_DIR, `${MAC_LABEL}.plist`);
|
|
26
|
-
const MAC_PLIST_PATH_OLD = join(MAC_PLIST_DIR, `${MAC_LABEL_OLD}.plist`);
|
|
27
|
-
|
|
28
|
-
const LINUX_SERVICE_NAME = "clawpilot.service";
|
|
29
|
-
const LINUX_SYSTEMD_USER_DIR = join(homedir(), ".config", "systemd", "user");
|
|
30
|
-
const LINUX_SERVICE_PATH = join(LINUX_SYSTEMD_USER_DIR, LINUX_SERVICE_NAME);
|
|
31
|
-
|
|
32
|
-
const LOG_DIR = join(homedir(), ".clawai");
|
|
33
|
-
const LOG_PATH = join(LOG_DIR, "clawpilot.log");
|
|
34
|
-
const ERROR_LOG_PATH = join(LOG_DIR, "clawpilot-error.log");
|
|
35
|
-
const LINUX_NOHUP_PID_PATH = join(LOG_DIR, "clawpilot.pid");
|
|
36
|
-
const LINUX_NOHUP_START_SCRIPT_PATH = join(LOG_DIR, "clawpilot-start.sh");
|
|
37
|
-
const WINDOWS_PID_PATH = join(LOG_DIR, "clawpilot-windows.pid");
|
|
38
|
-
const WINDOWS_START_SCRIPT_PATH = join(LOG_DIR, "clawpilot-start.cmd");
|
|
39
|
-
const WINDOWS_TASK_NAME = "ClawPilot Relay Client";
|
|
40
|
-
const PM2_PROCESS_NAME = "clawpilot";
|
|
41
|
-
|
|
42
|
-
const require = createRequire(import.meta.url);
|
|
43
|
-
const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
44
|
-
const PACKAGE_ROOT = resolve(MODULE_DIR, "..", "..");
|
|
45
|
-
|
|
46
|
-
function detectPlatform(): ServicePlatform {
|
|
47
|
-
if (process.platform === "darwin") return "macos";
|
|
48
|
-
if (process.platform === "linux") return "linux";
|
|
49
|
-
if (process.platform === "win32") return "windows";
|
|
50
|
-
return "unsupported";
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function shellEscape(arg: string): string {
|
|
54
|
-
return `'${arg.replace(/'/g, `'\\''`)}'`;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function run(command: string, stdio: "pipe" | "inherit" = "pipe"): void {
|
|
58
|
-
execSync(command, { stdio });
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function runOutput(command: string): string {
|
|
62
|
-
return execSync(command, { stdio: ["pipe", "pipe", "pipe"], encoding: "utf-8" }).trim();
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function runResult(command: string): { ok: boolean; output: string } {
|
|
66
|
-
try {
|
|
67
|
-
return {
|
|
68
|
-
ok: true,
|
|
69
|
-
output: execSync(command, { stdio: ["pipe", "pipe", "pipe"], encoding: "utf-8" }).trim(),
|
|
70
|
-
};
|
|
71
|
-
} catch (error) {
|
|
72
|
-
const output = error instanceof Error && "stdout" in error
|
|
73
|
-
? `${String((error as { stdout?: string }).stdout ?? "")}\n${String((error as { stderr?: string }).stderr ?? "")}`.trim()
|
|
74
|
-
: "";
|
|
75
|
-
return { ok: false, output };
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function commandExists(command: string): boolean {
|
|
80
|
-
try {
|
|
81
|
-
if (process.platform === "win32") {
|
|
82
|
-
run(`where ${command}`, "pipe");
|
|
83
|
-
} else {
|
|
84
|
-
run(`command -v ${command}`, "pipe");
|
|
85
|
-
}
|
|
86
|
-
return true;
|
|
87
|
-
} catch {
|
|
88
|
-
return false;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function getProgramArgs(): string[] {
|
|
93
|
-
const nodeBin = process.execPath;
|
|
94
|
-
const scriptPath = process.argv[1];
|
|
95
|
-
return nodeBin === scriptPath ? [scriptPath, "run"] : [nodeBin, scriptPath, "run"];
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function ensureLogDir(): void {
|
|
99
|
-
mkdirSync(LOG_DIR, { recursive: true });
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function canUseSystemdUser(): boolean {
|
|
103
|
-
if (!commandExists("systemctl")) return false;
|
|
104
|
-
try {
|
|
105
|
-
run("systemctl --user show-environment", "pipe");
|
|
106
|
-
return true;
|
|
107
|
-
} catch {
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function getLinuxLingerHint(): string | undefined {
|
|
113
|
-
if (process.platform !== "linux" || !commandExists("loginctl")) return undefined;
|
|
114
|
-
try {
|
|
115
|
-
const value = runOutput(`loginctl show-user ${shellEscape(userInfo().username)} -p Linger --value`);
|
|
116
|
-
if (value.toLowerCase() === "yes") return undefined;
|
|
117
|
-
} catch {
|
|
118
|
-
return undefined;
|
|
119
|
-
}
|
|
120
|
-
return `sudo loginctl enable-linger ${userInfo().username}`;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function isPidRunning(pid: number): boolean {
|
|
124
|
-
try {
|
|
125
|
-
process.kill(pid, 0);
|
|
126
|
-
return true;
|
|
127
|
-
} catch {
|
|
128
|
-
return false;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function readNohupPid(): number | null {
|
|
133
|
-
return readPidFile(LINUX_NOHUP_PID_PATH);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function readWindowsPid(): number | null {
|
|
137
|
-
return readPidFile(WINDOWS_PID_PATH);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function readPidFile(path: string): number | null {
|
|
141
|
-
if (!existsSync(path)) return null;
|
|
142
|
-
try {
|
|
143
|
-
const raw = readFileSync(path, "utf-8").trim();
|
|
144
|
-
const pid = Number(raw);
|
|
145
|
-
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
146
|
-
} catch {
|
|
147
|
-
return null;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function removeNohupPidFile(): void {
|
|
152
|
-
removePidFile(LINUX_NOHUP_PID_PATH);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function removeWindowsPidFile(): void {
|
|
156
|
-
removePidFile(WINDOWS_PID_PATH);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function removePidFile(path: string): void {
|
|
160
|
-
if (existsSync(path)) {
|
|
161
|
-
unlinkSync(path);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function getNohupStartCommand(): string {
|
|
166
|
-
return `bash ${shellEscape(LINUX_NOHUP_START_SCRIPT_PATH)}`;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function writeLinuxNohupStartScript(): void {
|
|
170
|
-
const args = getProgramArgs().map(shellEscape).join(" ");
|
|
171
|
-
const script = `#!/usr/bin/env bash
|
|
172
|
-
set -euo pipefail
|
|
173
|
-
|
|
174
|
-
mkdir -p ${shellEscape(LOG_DIR)}
|
|
175
|
-
if [ -f ${shellEscape(LINUX_NOHUP_PID_PATH)} ]; then
|
|
176
|
-
pid="$(cat ${shellEscape(LINUX_NOHUP_PID_PATH)} 2>/dev/null || true)"
|
|
177
|
-
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
178
|
-
echo "clawpilot is already running (pid=$pid)"
|
|
179
|
-
exit 0
|
|
180
|
-
fi
|
|
181
|
-
fi
|
|
182
|
-
|
|
183
|
-
nohup ${args} >> ${shellEscape(LOG_PATH)} 2>> ${shellEscape(ERROR_LOG_PATH)} < /dev/null &
|
|
184
|
-
echo $! > ${shellEscape(LINUX_NOHUP_PID_PATH)}
|
|
185
|
-
echo "clawpilot started in nohup mode (pid=$(cat ${shellEscape(LINUX_NOHUP_PID_PATH)}))"
|
|
186
|
-
`;
|
|
187
|
-
writeFileSync(LINUX_NOHUP_START_SCRIPT_PATH, script, { encoding: "utf-8", mode: 0o755 });
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function writeWindowsStartScript(): void {
|
|
191
|
-
const args = getProgramArgs()
|
|
192
|
-
.map((arg) => `"${arg.replace(/"/g, '""')}"`)
|
|
193
|
-
.join(" ");
|
|
194
|
-
const script = `@echo off
|
|
195
|
-
setlocal
|
|
196
|
-
if not exist "${LOG_DIR}" mkdir "${LOG_DIR}"
|
|
197
|
-
${args} >> "${LOG_PATH}" 2>> "${ERROR_LOG_PATH}"
|
|
198
|
-
`;
|
|
199
|
-
writeFileSync(WINDOWS_START_SCRIPT_PATH, script, "utf-8");
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function installMacService(): boolean {
|
|
203
|
-
const argsXml = getProgramArgs().map((arg) => ` <string>${arg}</string>`).join("\n");
|
|
204
|
-
const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|
205
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
|
206
|
-
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
207
|
-
<plist version="1.0">
|
|
208
|
-
<dict>
|
|
209
|
-
<key>Label</key>
|
|
210
|
-
<string>${MAC_LABEL}</string>
|
|
211
|
-
<key>ProgramArguments</key>
|
|
212
|
-
<array>
|
|
213
|
-
${argsXml}
|
|
214
|
-
</array>
|
|
215
|
-
<key>RunAtLoad</key>
|
|
216
|
-
<true/>
|
|
217
|
-
<key>KeepAlive</key>
|
|
218
|
-
<true/>
|
|
219
|
-
<key>StandardOutPath</key>
|
|
220
|
-
<string>${LOG_PATH}</string>
|
|
221
|
-
<key>StandardErrorPath</key>
|
|
222
|
-
<string>${ERROR_LOG_PATH}</string>
|
|
223
|
-
</dict>
|
|
224
|
-
</plist>`;
|
|
225
|
-
|
|
226
|
-
mkdirSync(MAC_PLIST_DIR, { recursive: true });
|
|
227
|
-
ensureLogDir();
|
|
228
|
-
|
|
229
|
-
try {
|
|
230
|
-
run(`launchctl unload -w "${MAC_PLIST_PATH}"`);
|
|
231
|
-
} catch {
|
|
232
|
-
// Ignore if not loaded.
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
writeFileSync(MAC_PLIST_PATH, plistContent, "utf-8");
|
|
236
|
-
|
|
237
|
-
try {
|
|
238
|
-
run(`launchctl load -w "${MAC_PLIST_PATH}"`, "inherit");
|
|
239
|
-
return true;
|
|
240
|
-
} catch {
|
|
241
|
-
return false;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function installLinuxServiceSystemd(): boolean {
|
|
246
|
-
mkdirSync(LINUX_SYSTEMD_USER_DIR, { recursive: true });
|
|
247
|
-
ensureLogDir();
|
|
248
|
-
|
|
249
|
-
const args = getProgramArgs().map(shellEscape).join(" ");
|
|
250
|
-
const serviceContent = `[Unit]
|
|
251
|
-
Description=ClawPilot relay client
|
|
252
|
-
After=network-online.target
|
|
253
|
-
Wants=network-online.target
|
|
254
|
-
|
|
255
|
-
[Service]
|
|
256
|
-
Type=simple
|
|
257
|
-
ExecStart=${args}
|
|
258
|
-
Restart=always
|
|
259
|
-
RestartSec=5
|
|
260
|
-
WorkingDirectory=${shellEscape(process.cwd())}
|
|
261
|
-
StandardOutput=append:${LOG_PATH}
|
|
262
|
-
StandardError=append:${ERROR_LOG_PATH}
|
|
263
|
-
|
|
264
|
-
[Install]
|
|
265
|
-
WantedBy=default.target
|
|
266
|
-
`;
|
|
267
|
-
|
|
268
|
-
writeFileSync(LINUX_SERVICE_PATH, serviceContent, "utf-8");
|
|
269
|
-
run("systemctl --user daemon-reload", "inherit");
|
|
270
|
-
run(`systemctl --user enable --now ${LINUX_SERVICE_NAME}`, "inherit");
|
|
271
|
-
return true;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
function installLinuxServiceNohup(): boolean {
|
|
275
|
-
ensureLogDir();
|
|
276
|
-
writeLinuxNohupStartScript();
|
|
277
|
-
run(`sh -lc ${shellEscape(getNohupStartCommand())}`, "inherit");
|
|
278
|
-
const pid = readNohupPid();
|
|
279
|
-
return pid != null && isPidRunning(pid);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function installLinuxService(): boolean {
|
|
283
|
-
if (isGlobalInstall() && canUsePm2()) {
|
|
284
|
-
try {
|
|
285
|
-
return installPm2Service();
|
|
286
|
-
} catch {
|
|
287
|
-
// Fall through to legacy modes below.
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
if (canUseSystemdUser()) {
|
|
292
|
-
try {
|
|
293
|
-
uninstallLinuxArtifacts(false);
|
|
294
|
-
return installLinuxServiceSystemd();
|
|
295
|
-
} catch {
|
|
296
|
-
// Fall through to nohup below.
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
try {
|
|
301
|
-
return installLinuxServiceNohup();
|
|
302
|
-
} catch {
|
|
303
|
-
return false;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
function canUseWindowsTaskScheduler(): boolean {
|
|
308
|
-
return process.platform === "win32" && commandExists("schtasks");
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
function getGlobalNodeModulesRoot(): string | null {
|
|
312
|
-
const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
313
|
-
try {
|
|
314
|
-
return realpathSync(runOutput(`${npmCommand} root -g`));
|
|
315
|
-
} catch {
|
|
316
|
-
return null;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
function isGlobalInstall(): boolean {
|
|
321
|
-
const globalRoot = getGlobalNodeModulesRoot();
|
|
322
|
-
if (!globalRoot) return false;
|
|
323
|
-
try {
|
|
324
|
-
const packageRoot = realpathSync(PACKAGE_ROOT);
|
|
325
|
-
return packageRoot === globalRoot || packageRoot.startsWith(`${globalRoot}${process.platform === "win32" ? "\\" : "/"}`);
|
|
326
|
-
} catch {
|
|
327
|
-
return false;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
function resolvePm2CliPath(): string | null {
|
|
332
|
-
try {
|
|
333
|
-
return require.resolve("pm2/bin/pm2");
|
|
334
|
-
} catch {
|
|
335
|
-
return null;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
function canUsePm2(): boolean {
|
|
340
|
-
return resolvePm2CliPath() != null;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function getPm2StartupHint(): string {
|
|
344
|
-
return "pm2 startup && pm2 save";
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
function runPm2(args: string[]): { ok: boolean; output: string } {
|
|
348
|
-
const cliPath = resolvePm2CliPath();
|
|
349
|
-
if (!cliPath) {
|
|
350
|
-
return { ok: false, output: "pm2 cli not found" };
|
|
351
|
-
}
|
|
352
|
-
const command = `${shellEscape(process.execPath)} ${shellEscape(cliPath)} ${args.map(shellEscape).join(" ")}`;
|
|
353
|
-
return runResult(command);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
function isPm2ProcessInstalled(): boolean {
|
|
357
|
-
const result = runPm2(["describe", PM2_PROCESS_NAME]);
|
|
358
|
-
return result.ok;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
function isPm2ProcessRunning(): boolean {
|
|
362
|
-
const result = runPm2(["jlist"]);
|
|
363
|
-
if (!result.ok || !result.output) return false;
|
|
364
|
-
try {
|
|
365
|
-
const processes = JSON.parse(result.output) as Array<{ name?: string; pm2_env?: { status?: string } }>;
|
|
366
|
-
return processes.some((entry) => entry.name === PM2_PROCESS_NAME && entry.pm2_env?.status === "online");
|
|
367
|
-
} catch {
|
|
368
|
-
return false;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
function installPm2Service(): boolean {
|
|
373
|
-
if (isPm2ProcessInstalled()) {
|
|
374
|
-
const restarted = restartPm2Service();
|
|
375
|
-
if (restarted) {
|
|
376
|
-
runPm2(["save"]);
|
|
377
|
-
runPm2(["startup"]);
|
|
378
|
-
}
|
|
379
|
-
return restarted;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
const args = getProgramArgs();
|
|
383
|
-
const start = runPm2([
|
|
384
|
-
"start",
|
|
385
|
-
args[0],
|
|
386
|
-
"--name",
|
|
387
|
-
PM2_PROCESS_NAME,
|
|
388
|
-
"--time",
|
|
389
|
-
"--output",
|
|
390
|
-
LOG_PATH,
|
|
391
|
-
"--error",
|
|
392
|
-
ERROR_LOG_PATH,
|
|
393
|
-
"--",
|
|
394
|
-
...args.slice(1),
|
|
395
|
-
]);
|
|
396
|
-
if (!start.ok) return false;
|
|
397
|
-
runPm2(["save"]);
|
|
398
|
-
runPm2(["startup"]);
|
|
399
|
-
return isPm2ProcessInstalled();
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
function restartPm2Service(): boolean {
|
|
403
|
-
if (!isPm2ProcessInstalled()) return false;
|
|
404
|
-
const result = runPm2(["restart", PM2_PROCESS_NAME]);
|
|
405
|
-
return result.ok;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
function stopPm2Service(removeProcess: boolean): boolean {
|
|
409
|
-
if (!isPm2ProcessInstalled()) return false;
|
|
410
|
-
const command = removeProcess ? "delete" : "stop";
|
|
411
|
-
const result = runPm2([command, PM2_PROCESS_NAME]);
|
|
412
|
-
if (removeProcess) {
|
|
413
|
-
runPm2(["save"]);
|
|
414
|
-
}
|
|
415
|
-
return result.ok;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
function isWindowsTaskInstalled(): boolean {
|
|
419
|
-
if (!canUseWindowsTaskScheduler()) return false;
|
|
420
|
-
try {
|
|
421
|
-
run(`schtasks /Query /TN "${WINDOWS_TASK_NAME}"`, "pipe");
|
|
422
|
-
return true;
|
|
423
|
-
} catch {
|
|
424
|
-
return false;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
function isWindowsTaskRunning(): boolean {
|
|
429
|
-
if (!isWindowsTaskInstalled() || !commandExists("powershell")) return false;
|
|
430
|
-
try {
|
|
431
|
-
const state = runOutput(
|
|
432
|
-
`powershell -NoProfile -Command "(Get-ScheduledTask -TaskName '${WINDOWS_TASK_NAME.replace(/'/g, "''")}').State"`
|
|
433
|
-
);
|
|
434
|
-
return state.toLowerCase().includes("running");
|
|
435
|
-
} catch {
|
|
436
|
-
return false;
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
function installWindowsTaskScheduler(): boolean {
|
|
441
|
-
ensureLogDir();
|
|
442
|
-
writeWindowsStartScript();
|
|
443
|
-
uninstallWindowsArtifacts(false);
|
|
444
|
-
const taskCommand = `"${WINDOWS_START_SCRIPT_PATH.replace(/"/g, '""')}"`;
|
|
445
|
-
run(
|
|
446
|
-
`schtasks /Create /TN "${WINDOWS_TASK_NAME}" /SC ONLOGON /RL LIMITED /TR ${taskCommand} /F`,
|
|
447
|
-
"inherit"
|
|
448
|
-
);
|
|
449
|
-
try {
|
|
450
|
-
run(`schtasks /Run /TN "${WINDOWS_TASK_NAME}"`, "inherit");
|
|
451
|
-
} catch {
|
|
452
|
-
// Best effort: task is installed even if immediate start fails.
|
|
453
|
-
}
|
|
454
|
-
return isWindowsTaskInstalled();
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
function installWindowsDetachedService(): boolean {
|
|
458
|
-
ensureLogDir();
|
|
459
|
-
|
|
460
|
-
const pid = readWindowsPid();
|
|
461
|
-
if (pid != null && isPidRunning(pid)) {
|
|
462
|
-
return true;
|
|
463
|
-
}
|
|
464
|
-
if (pid != null) {
|
|
465
|
-
removeWindowsPidFile();
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
const args = getProgramArgs();
|
|
469
|
-
const stdoutFd = openSync(LOG_PATH, "a");
|
|
470
|
-
const stderrFd = openSync(ERROR_LOG_PATH, "a");
|
|
471
|
-
const child = spawn(args[0], args.slice(1), {
|
|
472
|
-
detached: true,
|
|
473
|
-
stdio: ["ignore", stdoutFd, stderrFd],
|
|
474
|
-
windowsHide: true,
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
child.unref();
|
|
478
|
-
writeFileSync(WINDOWS_PID_PATH, `${child.pid}`, "utf-8");
|
|
479
|
-
return isPidRunning(child.pid ?? -1);
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
function installWindowsService(): boolean {
|
|
483
|
-
if (isGlobalInstall() && canUsePm2()) {
|
|
484
|
-
try {
|
|
485
|
-
return installPm2Service();
|
|
486
|
-
} catch {
|
|
487
|
-
// Fall back to legacy modes below.
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
if (canUseWindowsTaskScheduler()) {
|
|
492
|
-
try {
|
|
493
|
-
return installWindowsTaskScheduler();
|
|
494
|
-
} catch {
|
|
495
|
-
// Fall back to legacy detached mode below.
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
return installWindowsDetachedService();
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
function uninstallMacArtifacts(): boolean {
|
|
503
|
-
let changed = false;
|
|
504
|
-
try {
|
|
505
|
-
run(`launchctl unload -w "${MAC_PLIST_PATH}"`);
|
|
506
|
-
changed = true;
|
|
507
|
-
} catch {
|
|
508
|
-
// ignore
|
|
509
|
-
}
|
|
510
|
-
if (existsSync(MAC_PLIST_PATH)) {
|
|
511
|
-
unlinkSync(MAC_PLIST_PATH);
|
|
512
|
-
changed = true;
|
|
513
|
-
}
|
|
514
|
-
try {
|
|
515
|
-
run(`launchctl unload -w "${MAC_PLIST_PATH_OLD}"`);
|
|
516
|
-
changed = true;
|
|
517
|
-
} catch {
|
|
518
|
-
// ignore
|
|
519
|
-
}
|
|
520
|
-
if (existsSync(MAC_PLIST_PATH_OLD)) {
|
|
521
|
-
unlinkSync(MAC_PLIST_PATH_OLD);
|
|
522
|
-
changed = true;
|
|
523
|
-
}
|
|
524
|
-
return changed;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
function uninstallLinuxArtifacts(removeFile: boolean): boolean {
|
|
528
|
-
let changed = false;
|
|
529
|
-
|
|
530
|
-
if (canUseSystemdUser()) {
|
|
531
|
-
try {
|
|
532
|
-
run(`systemctl --user stop ${LINUX_SERVICE_NAME}`);
|
|
533
|
-
changed = true;
|
|
534
|
-
} catch {
|
|
535
|
-
// ignore
|
|
536
|
-
}
|
|
537
|
-
try {
|
|
538
|
-
run(`systemctl --user disable ${LINUX_SERVICE_NAME}`);
|
|
539
|
-
changed = true;
|
|
540
|
-
} catch {
|
|
541
|
-
// ignore
|
|
542
|
-
}
|
|
543
|
-
try {
|
|
544
|
-
run("systemctl --user daemon-reload");
|
|
545
|
-
} catch {
|
|
546
|
-
// ignore
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
const nohupPid = readNohupPid();
|
|
551
|
-
if (nohupPid != null) {
|
|
552
|
-
try {
|
|
553
|
-
process.kill(nohupPid, "SIGTERM");
|
|
554
|
-
changed = true;
|
|
555
|
-
} catch {
|
|
556
|
-
// ignore
|
|
557
|
-
}
|
|
558
|
-
removeNohupPidFile();
|
|
559
|
-
changed = true;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
if (removeFile && existsSync(LINUX_SERVICE_PATH)) {
|
|
563
|
-
unlinkSync(LINUX_SERVICE_PATH);
|
|
564
|
-
changed = true;
|
|
565
|
-
}
|
|
566
|
-
if (removeFile && existsSync(LINUX_NOHUP_START_SCRIPT_PATH)) {
|
|
567
|
-
unlinkSync(LINUX_NOHUP_START_SCRIPT_PATH);
|
|
568
|
-
changed = true;
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
return changed;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
function restartMacService(): boolean {
|
|
575
|
-
return installMacService();
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
function restartLinuxService(): boolean {
|
|
579
|
-
if (isPm2ProcessInstalled()) {
|
|
580
|
-
try {
|
|
581
|
-
return restartPm2Service();
|
|
582
|
-
} catch {
|
|
583
|
-
// Fall through to legacy restart below.
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
if (canUseSystemdUser() && existsSync(LINUX_SERVICE_PATH)) {
|
|
588
|
-
try {
|
|
589
|
-
run("systemctl --user daemon-reload", "inherit");
|
|
590
|
-
run(`systemctl --user restart ${LINUX_SERVICE_NAME}`, "inherit");
|
|
591
|
-
return true;
|
|
592
|
-
} catch {
|
|
593
|
-
// Fall back to nohup restart below.
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
uninstallLinuxArtifacts(false);
|
|
598
|
-
try {
|
|
599
|
-
return installLinuxServiceNohup();
|
|
600
|
-
} catch {
|
|
601
|
-
return false;
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
function restartWindowsService(): boolean {
|
|
606
|
-
if (isPm2ProcessInstalled()) {
|
|
607
|
-
try {
|
|
608
|
-
return restartPm2Service();
|
|
609
|
-
} catch {
|
|
610
|
-
// Fall through to legacy restart below.
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
if (isWindowsTaskInstalled()) {
|
|
615
|
-
try {
|
|
616
|
-
run(`schtasks /End /TN "${WINDOWS_TASK_NAME}"`, "inherit");
|
|
617
|
-
} catch {
|
|
618
|
-
// ignore if not running
|
|
619
|
-
}
|
|
620
|
-
try {
|
|
621
|
-
run(`schtasks /Run /TN "${WINDOWS_TASK_NAME}"`, "inherit");
|
|
622
|
-
return true;
|
|
623
|
-
} catch {
|
|
624
|
-
return false;
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
uninstallWindowsArtifacts(false);
|
|
629
|
-
try {
|
|
630
|
-
return installWindowsDetachedService();
|
|
631
|
-
} catch {
|
|
632
|
-
return false;
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
function uninstallWindowsArtifacts(removeFile: boolean): boolean {
|
|
637
|
-
let changed = false;
|
|
638
|
-
if (isWindowsTaskInstalled()) {
|
|
639
|
-
try {
|
|
640
|
-
run(`schtasks /End /TN "${WINDOWS_TASK_NAME}"`, "pipe");
|
|
641
|
-
changed = true;
|
|
642
|
-
} catch {
|
|
643
|
-
// ignore if not running
|
|
644
|
-
}
|
|
645
|
-
if (removeFile) {
|
|
646
|
-
try {
|
|
647
|
-
run(`schtasks /Delete /TN "${WINDOWS_TASK_NAME}" /F`, "pipe");
|
|
648
|
-
changed = true;
|
|
649
|
-
} catch {
|
|
650
|
-
// ignore
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
const pid = readWindowsPid();
|
|
655
|
-
if (pid != null) {
|
|
656
|
-
try {
|
|
657
|
-
process.kill(pid, "SIGTERM");
|
|
658
|
-
changed = true;
|
|
659
|
-
} catch {
|
|
660
|
-
// ignore
|
|
661
|
-
}
|
|
662
|
-
removeWindowsPidFile();
|
|
663
|
-
changed = true;
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
if (removeFile && existsSync(WINDOWS_PID_PATH)) {
|
|
667
|
-
unlinkSync(WINDOWS_PID_PATH);
|
|
668
|
-
changed = true;
|
|
669
|
-
}
|
|
670
|
-
if (removeFile && existsSync(WINDOWS_START_SCRIPT_PATH)) {
|
|
671
|
-
unlinkSync(WINDOWS_START_SCRIPT_PATH);
|
|
672
|
-
changed = true;
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
return changed;
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
export function getServicePlatform(): ServicePlatform {
|
|
679
|
-
return detectPlatform();
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
export function installService(): boolean {
|
|
683
|
-
switch (detectPlatform()) {
|
|
684
|
-
case "macos":
|
|
685
|
-
return installMacService();
|
|
686
|
-
case "linux":
|
|
687
|
-
return installLinuxService();
|
|
688
|
-
case "windows":
|
|
689
|
-
return installWindowsService();
|
|
690
|
-
default:
|
|
691
|
-
return false;
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
export function restartService(): boolean {
|
|
696
|
-
switch (detectPlatform()) {
|
|
697
|
-
case "macos":
|
|
698
|
-
return restartMacService();
|
|
699
|
-
case "linux":
|
|
700
|
-
return restartLinuxService();
|
|
701
|
-
case "windows":
|
|
702
|
-
return restartWindowsService();
|
|
703
|
-
default:
|
|
704
|
-
return false;
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
export function stopService(): boolean {
|
|
709
|
-
switch (detectPlatform()) {
|
|
710
|
-
case "macos":
|
|
711
|
-
return uninstallMacArtifacts();
|
|
712
|
-
case "linux":
|
|
713
|
-
if (isPm2ProcessInstalled()) return stopPm2Service(false);
|
|
714
|
-
return uninstallLinuxArtifacts(false);
|
|
715
|
-
case "windows":
|
|
716
|
-
if (isPm2ProcessInstalled()) return stopPm2Service(false);
|
|
717
|
-
return uninstallWindowsArtifacts(false);
|
|
718
|
-
default:
|
|
719
|
-
return false;
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
export function uninstallService(): boolean {
|
|
724
|
-
switch (detectPlatform()) {
|
|
725
|
-
case "macos":
|
|
726
|
-
return uninstallMacArtifacts();
|
|
727
|
-
case "linux":
|
|
728
|
-
if (isPm2ProcessInstalled()) return stopPm2Service(true);
|
|
729
|
-
return uninstallLinuxArtifacts(true);
|
|
730
|
-
case "windows":
|
|
731
|
-
if (isPm2ProcessInstalled()) return stopPm2Service(true);
|
|
732
|
-
return uninstallWindowsArtifacts(true);
|
|
733
|
-
default:
|
|
734
|
-
return false;
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
export function getServiceStatus(): ServiceStatus {
|
|
739
|
-
const platform = detectPlatform();
|
|
740
|
-
|
|
741
|
-
if (platform === "macos") {
|
|
742
|
-
let running = false;
|
|
743
|
-
try {
|
|
744
|
-
run(`launchctl list ${MAC_LABEL}`);
|
|
745
|
-
running = true;
|
|
746
|
-
} catch {
|
|
747
|
-
running = false;
|
|
748
|
-
}
|
|
749
|
-
return {
|
|
750
|
-
platform,
|
|
751
|
-
installed: existsSync(MAC_PLIST_PATH),
|
|
752
|
-
running,
|
|
753
|
-
serviceName: MAC_LABEL,
|
|
754
|
-
manager: "launchd",
|
|
755
|
-
servicePath: MAC_PLIST_PATH,
|
|
756
|
-
logPath: LOG_PATH,
|
|
757
|
-
startHint: `launchctl start ${MAC_LABEL}`,
|
|
758
|
-
};
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
if (platform === "linux") {
|
|
762
|
-
if (isPm2ProcessInstalled()) {
|
|
763
|
-
return {
|
|
764
|
-
platform,
|
|
765
|
-
installed: true,
|
|
766
|
-
running: isPm2ProcessRunning(),
|
|
767
|
-
serviceName: PM2_PROCESS_NAME,
|
|
768
|
-
manager: "pm2",
|
|
769
|
-
servicePath: resolvePm2CliPath() ?? undefined,
|
|
770
|
-
logPath: LOG_PATH,
|
|
771
|
-
startHint: `pm2 start ${PM2_PROCESS_NAME}`,
|
|
772
|
-
autoStartHint: getPm2StartupHint(),
|
|
773
|
-
};
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
const hasSystemdServiceFile = existsSync(LINUX_SERVICE_PATH);
|
|
777
|
-
if (hasSystemdServiceFile && canUseSystemdUser()) {
|
|
778
|
-
let systemdRunning = false;
|
|
779
|
-
try {
|
|
780
|
-
run(`systemctl --user is-active --quiet ${LINUX_SERVICE_NAME}`);
|
|
781
|
-
systemdRunning = true;
|
|
782
|
-
} catch {
|
|
783
|
-
systemdRunning = false;
|
|
784
|
-
}
|
|
785
|
-
return {
|
|
786
|
-
platform,
|
|
787
|
-
installed: true,
|
|
788
|
-
running: systemdRunning,
|
|
789
|
-
serviceName: LINUX_SERVICE_NAME,
|
|
790
|
-
manager: "systemd",
|
|
791
|
-
servicePath: LINUX_SERVICE_PATH,
|
|
792
|
-
logPath: LOG_PATH,
|
|
793
|
-
startHint: `systemctl --user start ${LINUX_SERVICE_NAME}`,
|
|
794
|
-
autoStartHint: getLinuxLingerHint(),
|
|
795
|
-
};
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
const pid = readNohupPid();
|
|
799
|
-
const hasNohupArtifacts = pid != null || existsSync(LINUX_NOHUP_START_SCRIPT_PATH);
|
|
800
|
-
const running = pid != null && isPidRunning(pid);
|
|
801
|
-
if (!running && pid != null) {
|
|
802
|
-
removeNohupPidFile();
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
if (running || hasNohupArtifacts) {
|
|
806
|
-
return {
|
|
807
|
-
platform,
|
|
808
|
-
installed: hasNohupArtifacts,
|
|
809
|
-
running,
|
|
810
|
-
serviceName: "clawpilot (nohup)",
|
|
811
|
-
manager: "nohup",
|
|
812
|
-
servicePath: existsSync(LINUX_NOHUP_START_SCRIPT_PATH) ? LINUX_NOHUP_START_SCRIPT_PATH : undefined,
|
|
813
|
-
logPath: LOG_PATH,
|
|
814
|
-
startHint: getNohupStartCommand(),
|
|
815
|
-
};
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
return {
|
|
819
|
-
platform,
|
|
820
|
-
installed: hasSystemdServiceFile,
|
|
821
|
-
running: false,
|
|
822
|
-
serviceName: LINUX_SERVICE_NAME,
|
|
823
|
-
manager: hasSystemdServiceFile ? "systemd" : "nohup",
|
|
824
|
-
servicePath: hasSystemdServiceFile ? LINUX_SERVICE_PATH : undefined,
|
|
825
|
-
logPath: LOG_PATH,
|
|
826
|
-
startHint: hasSystemdServiceFile ? `systemctl --user start ${LINUX_SERVICE_NAME}` : undefined,
|
|
827
|
-
autoStartHint: hasSystemdServiceFile ? getLinuxLingerHint() : undefined,
|
|
828
|
-
};
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
if (platform === "windows") {
|
|
832
|
-
if (isPm2ProcessInstalled()) {
|
|
833
|
-
return {
|
|
834
|
-
platform,
|
|
835
|
-
installed: true,
|
|
836
|
-
running: isPm2ProcessRunning(),
|
|
837
|
-
serviceName: PM2_PROCESS_NAME,
|
|
838
|
-
manager: "pm2",
|
|
839
|
-
servicePath: resolvePm2CliPath() ?? undefined,
|
|
840
|
-
logPath: LOG_PATH,
|
|
841
|
-
startHint: `pm2 restart ${PM2_PROCESS_NAME}`,
|
|
842
|
-
autoStartHint: getPm2StartupHint(),
|
|
843
|
-
};
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
if (isWindowsTaskInstalled()) {
|
|
847
|
-
return {
|
|
848
|
-
platform,
|
|
849
|
-
installed: true,
|
|
850
|
-
running: isWindowsTaskRunning(),
|
|
851
|
-
serviceName: WINDOWS_TASK_NAME,
|
|
852
|
-
manager: "task-scheduler",
|
|
853
|
-
servicePath: existsSync(WINDOWS_START_SCRIPT_PATH) ? WINDOWS_START_SCRIPT_PATH : undefined,
|
|
854
|
-
logPath: LOG_PATH,
|
|
855
|
-
startHint: `schtasks /Run /TN "${WINDOWS_TASK_NAME}"`,
|
|
856
|
-
};
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
const pid = readWindowsPid();
|
|
860
|
-
const running = pid != null && isPidRunning(pid);
|
|
861
|
-
if (!running && pid != null) {
|
|
862
|
-
removeWindowsPidFile();
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
return {
|
|
866
|
-
platform,
|
|
867
|
-
installed: pid != null,
|
|
868
|
-
running,
|
|
869
|
-
serviceName: "clawpilot (detached)",
|
|
870
|
-
manager: "windows-detached",
|
|
871
|
-
servicePath: existsSync(WINDOWS_PID_PATH) ? WINDOWS_PID_PATH : undefined,
|
|
872
|
-
logPath: LOG_PATH,
|
|
873
|
-
startHint: "clawpilot install",
|
|
874
|
-
};
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
return {
|
|
878
|
-
platform,
|
|
879
|
-
installed: false,
|
|
880
|
-
running: false,
|
|
881
|
-
serviceName: "",
|
|
882
|
-
manager: "unsupported",
|
|
883
|
-
logPath: LOG_PATH,
|
|
884
|
-
};
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
export const servicePaths = {
|
|
888
|
-
logPath: LOG_PATH,
|
|
889
|
-
errorLogPath: ERROR_LOG_PATH,
|
|
890
|
-
macPlistPath: MAC_PLIST_PATH,
|
|
891
|
-
linuxServicePath: LINUX_SERVICE_PATH,
|
|
892
|
-
linuxNohupPidPath: LINUX_NOHUP_PID_PATH,
|
|
893
|
-
linuxNohupStartScriptPath: LINUX_NOHUP_START_SCRIPT_PATH,
|
|
894
|
-
windowsPidPath: WINDOWS_PID_PATH,
|
|
895
|
-
windowsStartScriptPath: WINDOWS_START_SCRIPT_PATH,
|
|
896
|
-
windowsTaskName: WINDOWS_TASK_NAME,
|
|
897
|
-
pm2ProcessName: PM2_PROCESS_NAME,
|
|
898
|
-
};
|
|
899
|
-
|
|
900
|
-
export function supportsManagedServiceInstall(): boolean {
|
|
901
|
-
const platform = detectPlatform();
|
|
902
|
-
if (platform === "unsupported") return false;
|
|
903
|
-
if (platform === "macos") return true;
|
|
904
|
-
return isGlobalInstall() && canUsePm2();
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
export function getManagedServiceInstallHint(): string | undefined {
|
|
908
|
-
const platform = detectPlatform();
|
|
909
|
-
if (platform === "macos" || platform === "unsupported") return undefined;
|
|
910
|
-
if (!isGlobalInstall()) {
|
|
911
|
-
return process.platform === "win32"
|
|
912
|
-
? "npm install -g @rethinkingstudio/clawpilot"
|
|
913
|
-
: "npm install -g @rethinkingstudio/clawpilot";
|
|
914
|
-
}
|
|
915
|
-
if (!canUsePm2()) {
|
|
916
|
-
return "pm2 dependency is missing from the current installation";
|
|
917
|
-
}
|
|
918
|
-
return undefined;
|
|
919
|
-
}
|