xc-copilot-api 1.0.5 → 1.1.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/README.md +2 -0
- package/dist/daemon-main.js +810 -0
- package/dist/daemon-main.js.map +1 -0
- package/dist/main.js +56 -19
- package/dist/main.js.map +1 -1
- package/package.json +5 -8
|
@@ -0,0 +1,810 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { defineCommand, runMain } from "citty";
|
|
3
|
+
import { execSync, spawnSync } from "node:child_process";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
|
|
8
|
+
//#region src/daemon.ts
|
|
9
|
+
const APP_DIR = path.join(os.homedir(), ".local", "share", "copilot-api");
|
|
10
|
+
const LOG_FILE = path.join(APP_DIR, "copilot-api.log");
|
|
11
|
+
const ERR_FILE = path.join(APP_DIR, "copilot-api.err");
|
|
12
|
+
const LAUNCHD_LABEL = "com.xc-copilot-api";
|
|
13
|
+
const SYSTEMD_UNIT = "xc-copilot-api.service";
|
|
14
|
+
const WINDOWS_RUN_KEY_NAME = "XcCopilotApi";
|
|
15
|
+
function plistPath() {
|
|
16
|
+
return path.join(os.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
17
|
+
}
|
|
18
|
+
function systemdUnitPath() {
|
|
19
|
+
return path.join(os.homedir(), ".config", "systemd", "user", SYSTEMD_UNIT);
|
|
20
|
+
}
|
|
21
|
+
function launcherPath() {
|
|
22
|
+
return path.join(APP_DIR, "launcher.sh");
|
|
23
|
+
}
|
|
24
|
+
function buildStartArgs(args) {
|
|
25
|
+
const cmd = ["xc-copilot-api", "start"];
|
|
26
|
+
if (args.port) cmd.push("--port", args.port);
|
|
27
|
+
if (args.verbose) cmd.push("--verbose");
|
|
28
|
+
if (args.accountType && args.accountType !== "individual") cmd.push("--account-type", args.accountType);
|
|
29
|
+
if (args.githubToken) cmd.push("--github-token", args.githubToken);
|
|
30
|
+
if (args.proxyEnv) cmd.push("--proxy-env");
|
|
31
|
+
return cmd;
|
|
32
|
+
}
|
|
33
|
+
function buildNpxCommand(args) {
|
|
34
|
+
return ["npx", ...buildStartArgs(args)].map(shellQuote).join(" ");
|
|
35
|
+
}
|
|
36
|
+
function buildDirectCommand(args) {
|
|
37
|
+
return buildStartArgs(args).map(shellQuote).join(" ");
|
|
38
|
+
}
|
|
39
|
+
function shellQuote(s) {
|
|
40
|
+
if (/^[\w./:@=-]+$/.test(s)) return s;
|
|
41
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
42
|
+
}
|
|
43
|
+
function installMacOS(args) {
|
|
44
|
+
const plist = plistPath();
|
|
45
|
+
const launcher = launcherPath();
|
|
46
|
+
fs.mkdirSync(APP_DIR, { recursive: true });
|
|
47
|
+
fs.mkdirSync(path.dirname(plist), { recursive: true });
|
|
48
|
+
const execCmd = args.npx ? buildNpxCommand(args) : buildDirectCommand(args);
|
|
49
|
+
fs.writeFileSync(launcher, [
|
|
50
|
+
"#!/bin/zsh -l",
|
|
51
|
+
"[ -f \"$HOME/.zshrc\" ] && source \"$HOME/.zshrc\"",
|
|
52
|
+
`exec ${execCmd}`,
|
|
53
|
+
""
|
|
54
|
+
].join("\n"));
|
|
55
|
+
fs.chmodSync(launcher, 493);
|
|
56
|
+
const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|
57
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
58
|
+
<plist version="1.0">
|
|
59
|
+
<dict>
|
|
60
|
+
<key>Label</key>
|
|
61
|
+
<string>${LAUNCHD_LABEL}</string>
|
|
62
|
+
<key>ProgramArguments</key>
|
|
63
|
+
<array>
|
|
64
|
+
<string>${launcher}</string>
|
|
65
|
+
</array>
|
|
66
|
+
<key>WorkingDirectory</key>
|
|
67
|
+
<string>${os.homedir()}</string>
|
|
68
|
+
<key>RunAtLoad</key>
|
|
69
|
+
<true/>
|
|
70
|
+
<key>KeepAlive</key>
|
|
71
|
+
<true/>
|
|
72
|
+
<key>StandardOutPath</key>
|
|
73
|
+
<string>${LOG_FILE}</string>
|
|
74
|
+
<key>StandardErrorPath</key>
|
|
75
|
+
<string>${ERR_FILE}</string>
|
|
76
|
+
</dict>
|
|
77
|
+
</plist>
|
|
78
|
+
`;
|
|
79
|
+
fs.writeFileSync(plist, plistContent);
|
|
80
|
+
console.log(`Installed LaunchAgent '${LAUNCHD_LABEL}'`);
|
|
81
|
+
console.log(` Plist: ${plist}`);
|
|
82
|
+
console.log(` Launcher: ${launcher}`);
|
|
83
|
+
console.log(` Log: ${LOG_FILE}`);
|
|
84
|
+
console.log(` Mode: ${args.npx ? "npx (auto-update)" : "direct"}`);
|
|
85
|
+
console.log(` Start: xc-copilot-api-daemon restart`);
|
|
86
|
+
}
|
|
87
|
+
function uninstallMacOS() {
|
|
88
|
+
stopMacOS();
|
|
89
|
+
const plist = plistPath();
|
|
90
|
+
if (fs.existsSync(plist)) {
|
|
91
|
+
fs.unlinkSync(plist);
|
|
92
|
+
console.log(`Removed LaunchAgent plist: ${plist}`);
|
|
93
|
+
}
|
|
94
|
+
const launcher = launcherPath();
|
|
95
|
+
if (fs.existsSync(launcher)) {
|
|
96
|
+
fs.unlinkSync(launcher);
|
|
97
|
+
console.log(`Removed launcher: ${launcher}`);
|
|
98
|
+
}
|
|
99
|
+
console.log(`Uninstalled daemon '${LAUNCHD_LABEL}'`);
|
|
100
|
+
}
|
|
101
|
+
function stopMacOS() {
|
|
102
|
+
const job = `${`gui/${process.getuid?.() ?? 501}`}/${LAUNCHD_LABEL}`;
|
|
103
|
+
const result = spawnSync("launchctl", ["bootout", job], {
|
|
104
|
+
encoding: "utf-8",
|
|
105
|
+
timeout: 15e3
|
|
106
|
+
});
|
|
107
|
+
if (result.status === 0) {
|
|
108
|
+
console.log(`Stopped LaunchAgent '${LAUNCHD_LABEL}'`);
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
const detail = (result.stderr || result.stdout || "").toLowerCase();
|
|
112
|
+
if (detail.includes("not find") || detail.includes("no such")) return true;
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
function startMacOS() {
|
|
116
|
+
const plist = plistPath();
|
|
117
|
+
if (!fs.existsSync(plist)) {
|
|
118
|
+
console.error(`LaunchAgent plist not found: ${plist}`);
|
|
119
|
+
console.error("Run 'xc-copilot-api-daemon install' first.");
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
const domain = `gui/${process.getuid?.() ?? 501}`;
|
|
123
|
+
const job = `${domain}/${LAUNCHD_LABEL}`;
|
|
124
|
+
const bootstrap = spawnSync("launchctl", [
|
|
125
|
+
"bootstrap",
|
|
126
|
+
domain,
|
|
127
|
+
plist
|
|
128
|
+
], {
|
|
129
|
+
encoding: "utf-8",
|
|
130
|
+
timeout: 1e4
|
|
131
|
+
});
|
|
132
|
+
if (bootstrap.status !== 0) {
|
|
133
|
+
const detail = (bootstrap.stderr || bootstrap.stdout || "").toLowerCase();
|
|
134
|
+
if (!detail.includes("already")) {
|
|
135
|
+
console.error(`launchctl bootstrap failed: ${detail.trim()}`);
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const kick = spawnSync("launchctl", [
|
|
140
|
+
"kickstart",
|
|
141
|
+
"-k",
|
|
142
|
+
job
|
|
143
|
+
], {
|
|
144
|
+
encoding: "utf-8",
|
|
145
|
+
timeout: 15e3
|
|
146
|
+
});
|
|
147
|
+
if (kick.status !== 0) {
|
|
148
|
+
console.error(`launchctl kickstart failed: ${(kick.stderr || kick.stdout || "").trim()}`);
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
console.log(`Started LaunchAgent '${LAUNCHD_LABEL}'`);
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
function statusMacOS() {
|
|
155
|
+
const plist = plistPath();
|
|
156
|
+
if (!fs.existsSync(plist)) {
|
|
157
|
+
console.log("Daemon: not installed");
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
console.log(`Daemon: installed`);
|
|
161
|
+
console.log(` Plist: ${plist}`);
|
|
162
|
+
const launcher = launcherPath();
|
|
163
|
+
if (fs.existsSync(launcher)) {
|
|
164
|
+
const execLine = fs.readFileSync(launcher, "utf-8").split("\n").find((l) => l.startsWith("exec "));
|
|
165
|
+
if (execLine) console.log(` Command: ${execLine.replace("exec ", "")}`);
|
|
166
|
+
}
|
|
167
|
+
const result = spawnSync("launchctl", ["list", LAUNCHD_LABEL], {
|
|
168
|
+
encoding: "utf-8",
|
|
169
|
+
timeout: 5e3
|
|
170
|
+
});
|
|
171
|
+
if (result.status === 0) {
|
|
172
|
+
const output = result.stdout.trim();
|
|
173
|
+
const pidLine = output.split("\n").find((l) => l.includes("PID") || l.match(/^\s*"PID"/));
|
|
174
|
+
const lastExitLine = output.split("\n").find((l) => l.includes("LastExitStatus"));
|
|
175
|
+
const pidMatch = output.match(/"PID"\s*=\s*(\d+)/);
|
|
176
|
+
const exitMatch = output.match(/"LastExitStatus"\s*=\s*(\d+)/);
|
|
177
|
+
if (pidMatch) console.log(` Status: running (PID ${pidMatch[1]})`);
|
|
178
|
+
else console.log(` Status: not running`);
|
|
179
|
+
if (exitMatch) console.log(` Last exit: ${exitMatch[1]}`);
|
|
180
|
+
if (pidLine);
|
|
181
|
+
if (lastExitLine);
|
|
182
|
+
} else console.log(" Status: not loaded");
|
|
183
|
+
if (fs.existsSync(LOG_FILE)) {
|
|
184
|
+
const stat = fs.statSync(LOG_FILE);
|
|
185
|
+
console.log(` Log: ${LOG_FILE} (${(stat.size / 1024).toFixed(1)} KB)`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function installLinux(args) {
|
|
189
|
+
const unitPath = systemdUnitPath();
|
|
190
|
+
const launcher = launcherPath();
|
|
191
|
+
fs.mkdirSync(APP_DIR, { recursive: true });
|
|
192
|
+
fs.mkdirSync(path.dirname(unitPath), { recursive: true });
|
|
193
|
+
const execCmd = args.npx ? buildNpxCommand(args) : buildDirectCommand(args);
|
|
194
|
+
fs.writeFileSync(launcher, [
|
|
195
|
+
"#!/bin/bash -l",
|
|
196
|
+
"[ -f \"$HOME/.bashrc\" ] && source \"$HOME/.bashrc\"",
|
|
197
|
+
`exec ${execCmd}`,
|
|
198
|
+
""
|
|
199
|
+
].join("\n"));
|
|
200
|
+
fs.chmodSync(launcher, 493);
|
|
201
|
+
const unitContent = `[Unit]
|
|
202
|
+
Description=xc-copilot-api - GitHub Copilot OpenAI Compatible API
|
|
203
|
+
After=network.target
|
|
204
|
+
|
|
205
|
+
[Service]
|
|
206
|
+
Type=simple
|
|
207
|
+
ExecStart=${launcher}
|
|
208
|
+
Restart=on-failure
|
|
209
|
+
RestartSec=10
|
|
210
|
+
StandardOutput=append:${LOG_FILE}
|
|
211
|
+
StandardError=append:${ERR_FILE}
|
|
212
|
+
|
|
213
|
+
[Install]
|
|
214
|
+
WantedBy=default.target
|
|
215
|
+
`;
|
|
216
|
+
fs.writeFileSync(unitPath, unitContent);
|
|
217
|
+
spawnSync("systemctl", ["--user", "daemon-reload"], { encoding: "utf-8" });
|
|
218
|
+
spawnSync("systemctl", [
|
|
219
|
+
"--user",
|
|
220
|
+
"enable",
|
|
221
|
+
SYSTEMD_UNIT
|
|
222
|
+
], { encoding: "utf-8" });
|
|
223
|
+
console.log(`Installed systemd unit '${SYSTEMD_UNIT}'`);
|
|
224
|
+
console.log(` Unit: ${unitPath}`);
|
|
225
|
+
console.log(` Launcher: ${launcher}`);
|
|
226
|
+
console.log(` Log: ${LOG_FILE}`);
|
|
227
|
+
console.log(` Mode: ${args.npx ? "npx (auto-update)" : "direct"}`);
|
|
228
|
+
console.log(` Start: xc-copilot-api-daemon restart`);
|
|
229
|
+
}
|
|
230
|
+
function uninstallLinux() {
|
|
231
|
+
spawnSync("systemctl", [
|
|
232
|
+
"--user",
|
|
233
|
+
"stop",
|
|
234
|
+
SYSTEMD_UNIT
|
|
235
|
+
], { encoding: "utf-8" });
|
|
236
|
+
spawnSync("systemctl", [
|
|
237
|
+
"--user",
|
|
238
|
+
"disable",
|
|
239
|
+
SYSTEMD_UNIT
|
|
240
|
+
], { encoding: "utf-8" });
|
|
241
|
+
const unitPath = systemdUnitPath();
|
|
242
|
+
if (fs.existsSync(unitPath)) {
|
|
243
|
+
fs.unlinkSync(unitPath);
|
|
244
|
+
spawnSync("systemctl", ["--user", "daemon-reload"], { encoding: "utf-8" });
|
|
245
|
+
}
|
|
246
|
+
const launcher = launcherPath();
|
|
247
|
+
if (fs.existsSync(launcher)) fs.unlinkSync(launcher);
|
|
248
|
+
console.log(`Uninstalled systemd unit '${SYSTEMD_UNIT}'`);
|
|
249
|
+
}
|
|
250
|
+
function startLinux() {
|
|
251
|
+
const result = spawnSync("systemctl", [
|
|
252
|
+
"--user",
|
|
253
|
+
"start",
|
|
254
|
+
SYSTEMD_UNIT
|
|
255
|
+
], {
|
|
256
|
+
encoding: "utf-8",
|
|
257
|
+
timeout: 1e4
|
|
258
|
+
});
|
|
259
|
+
if (result.status !== 0) {
|
|
260
|
+
console.error(`systemd start failed: ${(result.stderr || result.stdout || "").trim()}`);
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
console.log(`Started systemd unit '${SYSTEMD_UNIT}'`);
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
function stopLinux() {
|
|
267
|
+
const result = spawnSync("systemctl", [
|
|
268
|
+
"--user",
|
|
269
|
+
"stop",
|
|
270
|
+
SYSTEMD_UNIT
|
|
271
|
+
], {
|
|
272
|
+
encoding: "utf-8",
|
|
273
|
+
timeout: 15e3
|
|
274
|
+
});
|
|
275
|
+
if (result.status !== 0) {
|
|
276
|
+
const detail = (result.stderr || result.stdout || "").trim();
|
|
277
|
+
if (!detail.includes("not loaded")) {
|
|
278
|
+
console.error(`systemd stop failed: ${detail}`);
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
console.log(`Stopped systemd unit '${SYSTEMD_UNIT}'`);
|
|
283
|
+
return true;
|
|
284
|
+
}
|
|
285
|
+
function statusLinux() {
|
|
286
|
+
const unitPath = systemdUnitPath();
|
|
287
|
+
if (!fs.existsSync(unitPath)) {
|
|
288
|
+
console.log("Daemon: not installed");
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
console.log("Daemon: installed");
|
|
292
|
+
console.log(` Unit: ${unitPath}`);
|
|
293
|
+
const launcher = launcherPath();
|
|
294
|
+
if (fs.existsSync(launcher)) {
|
|
295
|
+
const execLine = fs.readFileSync(launcher, "utf-8").split("\n").find((l) => l.startsWith("exec "));
|
|
296
|
+
if (execLine) console.log(` Command: ${execLine.replace("exec ", "")}`);
|
|
297
|
+
}
|
|
298
|
+
const result = spawnSync("systemctl", [
|
|
299
|
+
"--user",
|
|
300
|
+
"status",
|
|
301
|
+
SYSTEMD_UNIT,
|
|
302
|
+
"--no-pager"
|
|
303
|
+
], {
|
|
304
|
+
encoding: "utf-8",
|
|
305
|
+
timeout: 5e3
|
|
306
|
+
});
|
|
307
|
+
console.log(result.stdout.trim());
|
|
308
|
+
}
|
|
309
|
+
function windowsAppDir() {
|
|
310
|
+
const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local");
|
|
311
|
+
return path.join(localAppData, "copilot-api");
|
|
312
|
+
}
|
|
313
|
+
function windowsLauncherVbsPath() {
|
|
314
|
+
return path.join(windowsAppDir(), "launcher.vbs");
|
|
315
|
+
}
|
|
316
|
+
function windowsLogFile() {
|
|
317
|
+
return path.join(windowsAppDir(), "copilot-api.log");
|
|
318
|
+
}
|
|
319
|
+
function buildWindowsCommand(args) {
|
|
320
|
+
const startArgs = buildStartArgs(args);
|
|
321
|
+
return (args.npx ? ["npx", ...startArgs] : startArgs).map((s) => s.includes(" ") ? `"${s}"` : s).join(" ");
|
|
322
|
+
}
|
|
323
|
+
function installWindows(args) {
|
|
324
|
+
const appDir = windowsAppDir();
|
|
325
|
+
const logFile = windowsLogFile();
|
|
326
|
+
const errFile = path.join(appDir, "copilot-api.err");
|
|
327
|
+
const launcherVbs = windowsLauncherVbsPath();
|
|
328
|
+
fs.mkdirSync(appDir, { recursive: true });
|
|
329
|
+
const execCmd = buildWindowsCommand(args);
|
|
330
|
+
const vbsContent = [
|
|
331
|
+
"Set WshShell = CreateObject(\"WScript.Shell\")",
|
|
332
|
+
`WshShell.Run "${`cmd /c cd /d ""${os.homedir()}"" ^& ${execCmd} >> ""${logFile}"" 2>> ""${errFile}""`}", 0, False`,
|
|
333
|
+
""
|
|
334
|
+
].join("\r\n");
|
|
335
|
+
fs.writeFileSync(launcherVbs, vbsContent);
|
|
336
|
+
const result = spawnSync("reg.exe", [
|
|
337
|
+
"add",
|
|
338
|
+
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run",
|
|
339
|
+
"/v",
|
|
340
|
+
WINDOWS_RUN_KEY_NAME,
|
|
341
|
+
"/t",
|
|
342
|
+
"REG_SZ",
|
|
343
|
+
"/d",
|
|
344
|
+
`wscript.exe "${launcherVbs}"`,
|
|
345
|
+
"/f"
|
|
346
|
+
], {
|
|
347
|
+
encoding: "utf-8",
|
|
348
|
+
timeout: 1e4
|
|
349
|
+
});
|
|
350
|
+
if (result.status !== 0) {
|
|
351
|
+
console.error(`Failed to register Run key: ${(result.stderr || "").trim()}`);
|
|
352
|
+
process.exit(1);
|
|
353
|
+
}
|
|
354
|
+
console.log(`Installed Windows Run key '${WINDOWS_RUN_KEY_NAME}'`);
|
|
355
|
+
console.log(` Launcher: ${launcherVbs}`);
|
|
356
|
+
console.log(` Log: ${logFile}`);
|
|
357
|
+
console.log(` Mode: ${args.npx ? "npx (auto-update)" : "direct"}`);
|
|
358
|
+
console.log(` Start: xc-copilot-api-daemon restart`);
|
|
359
|
+
}
|
|
360
|
+
function uninstallWindows() {
|
|
361
|
+
stopWindows();
|
|
362
|
+
spawnSync("reg.exe", [
|
|
363
|
+
"delete",
|
|
364
|
+
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run",
|
|
365
|
+
"/v",
|
|
366
|
+
WINDOWS_RUN_KEY_NAME,
|
|
367
|
+
"/f"
|
|
368
|
+
], {
|
|
369
|
+
encoding: "utf-8",
|
|
370
|
+
timeout: 1e4
|
|
371
|
+
});
|
|
372
|
+
const launcherVbs = windowsLauncherVbsPath();
|
|
373
|
+
if (fs.existsSync(launcherVbs)) fs.unlinkSync(launcherVbs);
|
|
374
|
+
const legacyCmdPath = path.join(windowsAppDir(), "launcher.cmd");
|
|
375
|
+
if (fs.existsSync(legacyCmdPath)) fs.unlinkSync(legacyCmdPath);
|
|
376
|
+
console.log(`Uninstalled Windows Run key '${WINDOWS_RUN_KEY_NAME}'`);
|
|
377
|
+
}
|
|
378
|
+
function startWindows() {
|
|
379
|
+
const launcherVbs = windowsLauncherVbsPath();
|
|
380
|
+
if (!fs.existsSync(launcherVbs)) {
|
|
381
|
+
console.error(`Launcher not found: ${launcherVbs}`);
|
|
382
|
+
console.error("Run 'xc-copilot-api-daemon install' first.");
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
spawnSync("wscript.exe", [launcherVbs], {
|
|
386
|
+
encoding: "utf-8",
|
|
387
|
+
timeout: 1e4,
|
|
388
|
+
detached: true,
|
|
389
|
+
stdio: "ignore"
|
|
390
|
+
});
|
|
391
|
+
console.log("Started xc-copilot-api (background)");
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
function findCopilotPids(filter) {
|
|
395
|
+
const wqlFilter = filter.replace(/\*/g, "%");
|
|
396
|
+
const result = spawnSync("powershell", [
|
|
397
|
+
"-NoProfile",
|
|
398
|
+
"-Command",
|
|
399
|
+
`Get-CimInstance Win32_Process -Filter "CommandLine LIKE '${wqlFilter}' AND ProcessId != $PID" | Select-Object -ExpandProperty ProcessId`
|
|
400
|
+
], {
|
|
401
|
+
encoding: "utf-8",
|
|
402
|
+
timeout: 1e4
|
|
403
|
+
});
|
|
404
|
+
if (result.status !== 0) return [];
|
|
405
|
+
return result.stdout.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
406
|
+
}
|
|
407
|
+
function stopWindows() {
|
|
408
|
+
const result = spawnSync("powershell", [
|
|
409
|
+
"-NoProfile",
|
|
410
|
+
"-Command",
|
|
411
|
+
`Get-CimInstance Win32_Process -Filter "CommandLine LIKE '%copilot-api%' AND NOT (CommandLine LIKE '%daemon%') AND ProcessId != $PID" | Select-Object -ExpandProperty ProcessId`
|
|
412
|
+
], {
|
|
413
|
+
encoding: "utf-8",
|
|
414
|
+
timeout: 1e4
|
|
415
|
+
});
|
|
416
|
+
const pids = (result.status === 0 ? result.stdout : "").split("\n").map((l) => l.trim()).filter(Boolean);
|
|
417
|
+
for (const pid of pids) spawnSync("taskkill", [
|
|
418
|
+
"/PID",
|
|
419
|
+
pid,
|
|
420
|
+
"/F"
|
|
421
|
+
], {
|
|
422
|
+
encoding: "utf-8",
|
|
423
|
+
timeout: 5e3
|
|
424
|
+
});
|
|
425
|
+
if (pids.length > 0) {
|
|
426
|
+
console.log(`Stopped ${pids.length} process(es)`);
|
|
427
|
+
return true;
|
|
428
|
+
}
|
|
429
|
+
console.log("No copilot-api processes found.");
|
|
430
|
+
return true;
|
|
431
|
+
}
|
|
432
|
+
function statusWindows() {
|
|
433
|
+
if (spawnSync("reg.exe", [
|
|
434
|
+
"query",
|
|
435
|
+
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run",
|
|
436
|
+
"/v",
|
|
437
|
+
WINDOWS_RUN_KEY_NAME
|
|
438
|
+
], {
|
|
439
|
+
encoding: "utf-8",
|
|
440
|
+
timeout: 5e3
|
|
441
|
+
}).status !== 0) {
|
|
442
|
+
console.log("Daemon: not installed");
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
console.log("Daemon: installed");
|
|
446
|
+
const launcherVbs = windowsLauncherVbsPath();
|
|
447
|
+
if (fs.existsSync(launcherVbs)) {
|
|
448
|
+
const match = fs.readFileSync(launcherVbs, "utf-8").match(/\^&\s*(.+?)\s*>>/);
|
|
449
|
+
if (match) console.log(` Command: ${match[1].trim()}`);
|
|
450
|
+
}
|
|
451
|
+
const pids = findCopilotPids("*copilot-api*start*");
|
|
452
|
+
console.log(pids.length > 0 ? ` Status: running (PID ${pids.join(", ")})` : " Status: not running");
|
|
453
|
+
const logFile = windowsLogFile();
|
|
454
|
+
if (fs.existsSync(logFile)) {
|
|
455
|
+
const stat = fs.statSync(logFile);
|
|
456
|
+
console.log(` Log: ${logFile} (${(stat.size / 1024).toFixed(1)} KB)`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
function findAllCopilotLaunchdJobs() {
|
|
460
|
+
const result = spawnSync("launchctl", ["list"], {
|
|
461
|
+
encoding: "utf-8",
|
|
462
|
+
timeout: 5e3
|
|
463
|
+
});
|
|
464
|
+
if (result.status !== 0) return [];
|
|
465
|
+
const jobs = [];
|
|
466
|
+
for (const line of result.stdout.split("\n")) {
|
|
467
|
+
if (!line.toLowerCase().includes("copilot")) continue;
|
|
468
|
+
const parts = line.trim().split(/\s+/);
|
|
469
|
+
if (parts.length < 3) continue;
|
|
470
|
+
const pid = parts[0] === "-" ? null : parts[0];
|
|
471
|
+
const label = parts[2];
|
|
472
|
+
const plist = path.join(os.homedir(), "Library", "LaunchAgents", `${label}.plist`);
|
|
473
|
+
jobs.push({
|
|
474
|
+
label,
|
|
475
|
+
pid,
|
|
476
|
+
plistPath: fs.existsSync(plist) ? plist : null
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
return jobs;
|
|
480
|
+
}
|
|
481
|
+
function findAllCopilotProcesses() {
|
|
482
|
+
try {
|
|
483
|
+
const output = execSync("ps aux | grep -i copilot-api | grep -v grep", {
|
|
484
|
+
encoding: "utf-8",
|
|
485
|
+
timeout: 5e3
|
|
486
|
+
});
|
|
487
|
+
const procs = [];
|
|
488
|
+
for (const line of output.trim().split("\n")) {
|
|
489
|
+
if (!line) continue;
|
|
490
|
+
const parts = line.trim().split(/\s+/);
|
|
491
|
+
if (parts.length < 11) continue;
|
|
492
|
+
const pid = parts[1];
|
|
493
|
+
const command = parts.slice(10).join(" ");
|
|
494
|
+
procs.push({
|
|
495
|
+
pid,
|
|
496
|
+
command
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
return procs;
|
|
500
|
+
} catch {
|
|
501
|
+
return [];
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
function findAllCopilotSystemdUnits() {
|
|
505
|
+
const result = spawnSync("systemctl", [
|
|
506
|
+
"--user",
|
|
507
|
+
"list-units",
|
|
508
|
+
"--all",
|
|
509
|
+
"--no-pager",
|
|
510
|
+
"--plain"
|
|
511
|
+
], {
|
|
512
|
+
encoding: "utf-8",
|
|
513
|
+
timeout: 5e3
|
|
514
|
+
});
|
|
515
|
+
if (result.status !== 0) return [];
|
|
516
|
+
const units = [];
|
|
517
|
+
for (const line of result.stdout.split("\n")) if (line.toLowerCase().includes("copilot")) {
|
|
518
|
+
const unit = line.trim().split(/\s+/)[0];
|
|
519
|
+
if (unit) units.push(unit);
|
|
520
|
+
}
|
|
521
|
+
return units;
|
|
522
|
+
}
|
|
523
|
+
function statusAll() {
|
|
524
|
+
if (isMacOS()) {
|
|
525
|
+
const jobs = findAllCopilotLaunchdJobs();
|
|
526
|
+
if (jobs.length > 0) {
|
|
527
|
+
console.log("=== LaunchAgent Jobs ===");
|
|
528
|
+
for (const job of jobs) {
|
|
529
|
+
console.log(`\n Label: ${job.label}`);
|
|
530
|
+
if (job.plistPath) console.log(` Plist: ${job.plistPath}`);
|
|
531
|
+
else console.log(` Plist: (not found)`);
|
|
532
|
+
console.log(` PID: ${job.pid ?? "not running"}`);
|
|
533
|
+
}
|
|
534
|
+
} else console.log("No copilot-api LaunchAgent jobs found.");
|
|
535
|
+
const procs = findAllCopilotProcesses();
|
|
536
|
+
if (procs.length > 0) {
|
|
537
|
+
console.log("\n=== Processes ===");
|
|
538
|
+
for (const proc of procs) console.log(` PID ${proc.pid}: ${proc.command}`);
|
|
539
|
+
}
|
|
540
|
+
} else {
|
|
541
|
+
const units = findAllCopilotSystemdUnits();
|
|
542
|
+
if (units.length > 0) {
|
|
543
|
+
console.log("=== Systemd Units ===");
|
|
544
|
+
for (const unit of units) {
|
|
545
|
+
const r = spawnSync("systemctl", [
|
|
546
|
+
"--user",
|
|
547
|
+
"status",
|
|
548
|
+
unit,
|
|
549
|
+
"--no-pager"
|
|
550
|
+
], {
|
|
551
|
+
encoding: "utf-8",
|
|
552
|
+
timeout: 5e3
|
|
553
|
+
});
|
|
554
|
+
console.log(`\n--- ${unit} ---`);
|
|
555
|
+
console.log(r.stdout.trim());
|
|
556
|
+
}
|
|
557
|
+
} else console.log("No copilot-api systemd units found.");
|
|
558
|
+
const procs = findAllCopilotProcesses();
|
|
559
|
+
if (procs.length > 0) {
|
|
560
|
+
console.log("\n=== Processes ===");
|
|
561
|
+
for (const proc of procs) console.log(` PID ${proc.pid}: ${proc.command}`);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
function stopAll() {
|
|
566
|
+
let stopped = 0;
|
|
567
|
+
if (isMacOS()) {
|
|
568
|
+
const jobs = findAllCopilotLaunchdJobs();
|
|
569
|
+
const domain = `gui/${process.getuid?.() ?? 501}`;
|
|
570
|
+
for (const job of jobs) {
|
|
571
|
+
const jobTarget = `${domain}/${job.label}`;
|
|
572
|
+
console.log(`Stopping LaunchAgent '${job.label}'...`);
|
|
573
|
+
spawnSync("launchctl", ["bootout", jobTarget], {
|
|
574
|
+
encoding: "utf-8",
|
|
575
|
+
timeout: 15e3
|
|
576
|
+
});
|
|
577
|
+
stopped++;
|
|
578
|
+
}
|
|
579
|
+
} else {
|
|
580
|
+
const units = findAllCopilotSystemdUnits();
|
|
581
|
+
for (const unit of units) {
|
|
582
|
+
console.log(`Stopping systemd unit '${unit}'...`);
|
|
583
|
+
spawnSync("systemctl", [
|
|
584
|
+
"--user",
|
|
585
|
+
"stop",
|
|
586
|
+
unit
|
|
587
|
+
], {
|
|
588
|
+
encoding: "utf-8",
|
|
589
|
+
timeout: 15e3
|
|
590
|
+
});
|
|
591
|
+
stopped++;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
const procs = findAllCopilotProcesses();
|
|
595
|
+
for (const proc of procs) {
|
|
596
|
+
console.log(`Killing PID ${proc.pid}: ${proc.command}`);
|
|
597
|
+
try {
|
|
598
|
+
process.kill(Number.parseInt(proc.pid, 10), "SIGTERM");
|
|
599
|
+
stopped++;
|
|
600
|
+
} catch {}
|
|
601
|
+
}
|
|
602
|
+
if (stopped === 0) console.log("No copilot-api processes found.");
|
|
603
|
+
else console.log(`\nStopped/killed ${stopped} item(s).`);
|
|
604
|
+
}
|
|
605
|
+
function isMacOS() {
|
|
606
|
+
return process.platform === "darwin";
|
|
607
|
+
}
|
|
608
|
+
function isLinux() {
|
|
609
|
+
return process.platform === "linux";
|
|
610
|
+
}
|
|
611
|
+
function isWindows() {
|
|
612
|
+
return process.platform === "win32";
|
|
613
|
+
}
|
|
614
|
+
function assertSupported() {
|
|
615
|
+
if (!isMacOS() && !isLinux() && !isWindows()) {
|
|
616
|
+
console.error("Daemon management is only supported on macOS, Linux, and Windows.");
|
|
617
|
+
process.exit(1);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
const installCmd = defineCommand({
|
|
621
|
+
meta: {
|
|
622
|
+
name: "install",
|
|
623
|
+
description: "Install xc-copilot-api daemon (launchd on macOS, systemd on Linux, Run key on Windows)"
|
|
624
|
+
},
|
|
625
|
+
args: {
|
|
626
|
+
npx: {
|
|
627
|
+
type: "boolean",
|
|
628
|
+
default: true,
|
|
629
|
+
description: "Use npx to run (auto-updates on restart). Set --no-npx to use direct binary."
|
|
630
|
+
},
|
|
631
|
+
port: {
|
|
632
|
+
alias: "p",
|
|
633
|
+
type: "string",
|
|
634
|
+
default: "4141",
|
|
635
|
+
description: "Port for the API server"
|
|
636
|
+
},
|
|
637
|
+
verbose: {
|
|
638
|
+
alias: "v",
|
|
639
|
+
type: "boolean",
|
|
640
|
+
default: false,
|
|
641
|
+
description: "Enable verbose logging"
|
|
642
|
+
},
|
|
643
|
+
"account-type": {
|
|
644
|
+
alias: "a",
|
|
645
|
+
type: "string",
|
|
646
|
+
default: "individual",
|
|
647
|
+
description: "Account type (individual, business, enterprise)"
|
|
648
|
+
},
|
|
649
|
+
"github-token": {
|
|
650
|
+
alias: "g",
|
|
651
|
+
type: "string",
|
|
652
|
+
description: "GitHub token to use"
|
|
653
|
+
},
|
|
654
|
+
"proxy-env": {
|
|
655
|
+
type: "boolean",
|
|
656
|
+
default: false,
|
|
657
|
+
description: "Initialize proxy from environment variables"
|
|
658
|
+
}
|
|
659
|
+
},
|
|
660
|
+
run({ args }) {
|
|
661
|
+
assertSupported();
|
|
662
|
+
const installArgs = {
|
|
663
|
+
npx: args.npx,
|
|
664
|
+
port: args.port,
|
|
665
|
+
verbose: args.verbose,
|
|
666
|
+
accountType: args["account-type"],
|
|
667
|
+
githubToken: args["github-token"],
|
|
668
|
+
proxyEnv: args["proxy-env"]
|
|
669
|
+
};
|
|
670
|
+
if (isMacOS()) installMacOS(installArgs);
|
|
671
|
+
else if (isWindows()) installWindows(installArgs);
|
|
672
|
+
else installLinux(installArgs);
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
const uninstallCmd = defineCommand({
|
|
676
|
+
meta: {
|
|
677
|
+
name: "uninstall",
|
|
678
|
+
description: "Uninstall the daemon"
|
|
679
|
+
},
|
|
680
|
+
run() {
|
|
681
|
+
assertSupported();
|
|
682
|
+
if (isMacOS()) uninstallMacOS();
|
|
683
|
+
else if (isWindows()) uninstallWindows();
|
|
684
|
+
else uninstallLinux();
|
|
685
|
+
}
|
|
686
|
+
});
|
|
687
|
+
const statusCmd = defineCommand({
|
|
688
|
+
meta: {
|
|
689
|
+
name: "status",
|
|
690
|
+
description: "Show daemon status (use --all to show all copilot-api instances)"
|
|
691
|
+
},
|
|
692
|
+
args: { all: {
|
|
693
|
+
type: "boolean",
|
|
694
|
+
default: false,
|
|
695
|
+
description: "Show all copilot-api related daemons and processes"
|
|
696
|
+
} },
|
|
697
|
+
run({ args }) {
|
|
698
|
+
assertSupported();
|
|
699
|
+
if (args.all) statusAll();
|
|
700
|
+
else if (isMacOS()) statusMacOS();
|
|
701
|
+
else if (isWindows()) statusWindows();
|
|
702
|
+
else statusLinux();
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
const restartCmd = defineCommand({
|
|
706
|
+
meta: {
|
|
707
|
+
name: "restart",
|
|
708
|
+
description: "Restart the daemon (npx mode fetches latest version automatically)"
|
|
709
|
+
},
|
|
710
|
+
run() {
|
|
711
|
+
assertSupported();
|
|
712
|
+
if (isMacOS()) {
|
|
713
|
+
if (!startMacOS()) process.exit(1);
|
|
714
|
+
} else if (isWindows()) {
|
|
715
|
+
stopWindows();
|
|
716
|
+
if (!startWindows()) process.exit(1);
|
|
717
|
+
} else {
|
|
718
|
+
stopLinux();
|
|
719
|
+
if (!startLinux()) process.exit(1);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
});
|
|
723
|
+
const stopCmd = defineCommand({
|
|
724
|
+
meta: {
|
|
725
|
+
name: "stop",
|
|
726
|
+
description: "Stop the daemon (use --all to kill all copilot-api instances)"
|
|
727
|
+
},
|
|
728
|
+
args: { all: {
|
|
729
|
+
type: "boolean",
|
|
730
|
+
default: false,
|
|
731
|
+
description: "Stop all copilot-api related daemons and kill all processes"
|
|
732
|
+
} },
|
|
733
|
+
run({ args }) {
|
|
734
|
+
assertSupported();
|
|
735
|
+
if (args.all) stopAll();
|
|
736
|
+
else if (isMacOS()) {
|
|
737
|
+
if (!stopMacOS()) process.exit(1);
|
|
738
|
+
} else if (isWindows()) {
|
|
739
|
+
if (!stopWindows()) process.exit(1);
|
|
740
|
+
} else if (!stopLinux()) process.exit(1);
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
const logsCmd = defineCommand({
|
|
744
|
+
meta: {
|
|
745
|
+
name: "logs",
|
|
746
|
+
description: "Show recent daemon logs"
|
|
747
|
+
},
|
|
748
|
+
args: {
|
|
749
|
+
follow: {
|
|
750
|
+
alias: "f",
|
|
751
|
+
type: "boolean",
|
|
752
|
+
default: false,
|
|
753
|
+
description: "Follow log output (like tail -f)"
|
|
754
|
+
},
|
|
755
|
+
lines: {
|
|
756
|
+
alias: "n",
|
|
757
|
+
type: "string",
|
|
758
|
+
default: "50",
|
|
759
|
+
description: "Number of lines to show"
|
|
760
|
+
}
|
|
761
|
+
},
|
|
762
|
+
run({ args }) {
|
|
763
|
+
assertSupported();
|
|
764
|
+
const logPath = isWindows() ? windowsLogFile() : LOG_FILE;
|
|
765
|
+
if (!fs.existsSync(logPath)) {
|
|
766
|
+
console.log("No log file found.");
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
if (isWindows()) if (args.follow) {
|
|
770
|
+
const { status } = spawnSync("powershell", ["-Command", `Get-Content -Path '${logPath}' -Wait -Tail ${args.lines}`], { stdio: "inherit" });
|
|
771
|
+
process.exit(status ?? 0);
|
|
772
|
+
} else {
|
|
773
|
+
const { status } = spawnSync("powershell", ["-Command", `Get-Content -Path '${logPath}' -Tail ${args.lines}`], { stdio: "inherit" });
|
|
774
|
+
process.exit(status ?? 0);
|
|
775
|
+
}
|
|
776
|
+
else if (args.follow) {
|
|
777
|
+
const { status } = spawnSync("tail", ["-f", logPath], { stdio: "inherit" });
|
|
778
|
+
process.exit(status ?? 0);
|
|
779
|
+
} else {
|
|
780
|
+
const { status } = spawnSync("tail", [
|
|
781
|
+
"-n",
|
|
782
|
+
args.lines,
|
|
783
|
+
logPath
|
|
784
|
+
], { stdio: "inherit" });
|
|
785
|
+
process.exit(status ?? 0);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
});
|
|
789
|
+
const daemon = defineCommand({
|
|
790
|
+
meta: {
|
|
791
|
+
name: "xc-copilot-api-daemon",
|
|
792
|
+
description: "Manage xc-copilot-api daemon (install, status, restart, stop, uninstall, logs)"
|
|
793
|
+
},
|
|
794
|
+
subCommands: {
|
|
795
|
+
install: installCmd,
|
|
796
|
+
uninstall: uninstallCmd,
|
|
797
|
+
status: statusCmd,
|
|
798
|
+
restart: restartCmd,
|
|
799
|
+
stop: stopCmd,
|
|
800
|
+
logs: logsCmd
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
//#endregion
|
|
805
|
+
//#region src/daemon-main.ts
|
|
806
|
+
await runMain(daemon);
|
|
807
|
+
|
|
808
|
+
//#endregion
|
|
809
|
+
export { };
|
|
810
|
+
//# sourceMappingURL=daemon-main.js.map
|