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.
@@ -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