xc-copilot-api 1.1.0 → 1.1.2

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.
@@ -5,19 +5,10 @@ import fs from "node:fs";
5
5
  import os from "node:os";
6
6
  import path from "node:path";
7
7
 
8
- //#region src/daemon.ts
8
+ //#region src/daemon/shared.ts
9
9
  const APP_DIR = path.join(os.homedir(), ".local", "share", "copilot-api");
10
10
  const LOG_FILE = path.join(APP_DIR, "copilot-api.log");
11
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
12
  function launcherPath() {
22
13
  return path.join(APP_DIR, "launcher.sh");
23
14
  }
@@ -30,15 +21,43 @@ function buildStartArgs(args) {
30
21
  if (args.proxyEnv) cmd.push("--proxy-env");
31
22
  return cmd;
32
23
  }
24
+ function shellQuote(s) {
25
+ if (/^[\w./:@=-]+$/.test(s)) return s;
26
+ return `'${s.replace(/'/g, "'\\''")}'`;
27
+ }
33
28
  function buildNpxCommand(args) {
34
- return ["npx", ...buildStartArgs(args)].map(shellQuote).join(" ");
29
+ const startArgs = buildStartArgs(args);
30
+ startArgs[0] = "xc-copilot-api@latest";
31
+ return [
32
+ "npx",
33
+ "-y",
34
+ ...startArgs
35
+ ].map(shellQuote).join(" ");
35
36
  }
36
37
  function buildDirectCommand(args) {
37
38
  return buildStartArgs(args).map(shellQuote).join(" ");
38
39
  }
39
- function shellQuote(s) {
40
- if (/^[\w./:@=-]+$/.test(s)) return s;
41
- return `'${s.replace(/'/g, "'\\''")}'`;
40
+ function isMacOS() {
41
+ return process.platform === "darwin";
42
+ }
43
+ function isLinux() {
44
+ return process.platform === "linux";
45
+ }
46
+ function isWindows() {
47
+ return process.platform === "win32";
48
+ }
49
+ function assertSupported() {
50
+ if (!isMacOS() && !isLinux() && !isWindows()) {
51
+ console.error("Daemon management is only supported on macOS, Linux, and Windows.");
52
+ process.exit(1);
53
+ }
54
+ }
55
+
56
+ //#endregion
57
+ //#region src/daemon/macos.ts
58
+ const LAUNCHD_LABEL = "com.xc-copilot-api";
59
+ function plistPath() {
60
+ return path.join(os.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
42
61
  }
43
62
  function installMacOS(args) {
44
63
  const plist = plistPath();
@@ -185,6 +204,35 @@ function statusMacOS() {
185
204
  console.log(` Log: ${LOG_FILE} (${(stat.size / 1024).toFixed(1)} KB)`);
186
205
  }
187
206
  }
207
+ function findAllCopilotLaunchdJobs() {
208
+ const result = spawnSync("launchctl", ["list"], {
209
+ encoding: "utf-8",
210
+ timeout: 5e3
211
+ });
212
+ if (result.status !== 0) return [];
213
+ const jobs = [];
214
+ for (const line of result.stdout.split("\n")) {
215
+ if (!line.toLowerCase().includes("copilot")) continue;
216
+ const parts = line.trim().split(/\s+/);
217
+ if (parts.length < 3) continue;
218
+ const pid = parts[0] === "-" ? null : parts[0];
219
+ const label = parts[2];
220
+ const plist = path.join(os.homedir(), "Library", "LaunchAgents", `${label}.plist`);
221
+ jobs.push({
222
+ label,
223
+ pid,
224
+ plistPath: fs.existsSync(plist) ? plist : null
225
+ });
226
+ }
227
+ return jobs;
228
+ }
229
+
230
+ //#endregion
231
+ //#region src/daemon/linux.ts
232
+ const SYSTEMD_UNIT = "xc-copilot-api.service";
233
+ function systemdUnitPath() {
234
+ return path.join(os.homedir(), ".config", "systemd", "user", SYSTEMD_UNIT);
235
+ }
188
236
  function installLinux(args) {
189
237
  const unitPath = systemdUnitPath();
190
238
  const launcher = launcherPath();
@@ -306,88 +354,116 @@ function statusLinux() {
306
354
  });
307
355
  console.log(result.stdout.trim());
308
356
  }
357
+ function findAllCopilotSystemdUnits() {
358
+ const result = spawnSync("systemctl", [
359
+ "--user",
360
+ "list-units",
361
+ "--all",
362
+ "--no-pager",
363
+ "--plain"
364
+ ], {
365
+ encoding: "utf-8",
366
+ timeout: 5e3
367
+ });
368
+ if (result.status !== 0) return [];
369
+ const units = [];
370
+ for (const line of result.stdout.split("\n")) if (line.toLowerCase().includes("copilot")) {
371
+ const unit = line.trim().split(/\s+/)[0];
372
+ if (unit) units.push(unit);
373
+ }
374
+ return units;
375
+ }
376
+
377
+ //#endregion
378
+ //#region src/daemon/windows.ts
379
+ const WINDOWS_TASK_NAME = "XcCopilotApi";
309
380
  function windowsAppDir() {
310
381
  const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local");
311
382
  return path.join(localAppData, "copilot-api");
312
383
  }
313
- function windowsLauncherVbsPath() {
314
- return path.join(windowsAppDir(), "launcher.vbs");
384
+ function windowsLauncherCmdPath() {
385
+ return path.join(windowsAppDir(), "launcher.cmd");
315
386
  }
316
387
  function windowsLogFile() {
317
388
  return path.join(windowsAppDir(), "copilot-api.log");
318
389
  }
319
390
  function buildWindowsCommand(args) {
320
391
  const startArgs = buildStartArgs(args);
321
- return (args.npx ? ["npx", ...startArgs] : startArgs).map((s) => s.includes(" ") ? `"${s}"` : s).join(" ");
392
+ if (args.npx) {
393
+ startArgs[0] = "xc-copilot-api@latest";
394
+ return [
395
+ "npx",
396
+ "-y",
397
+ ...startArgs
398
+ ].map((s) => s.includes(" ") ? `"${s}"` : s).join(" ");
399
+ }
400
+ return startArgs.map((s) => s.includes(" ") ? `"${s}"` : s).join(" ");
322
401
  }
323
402
  function installWindows(args) {
324
403
  const appDir = windowsAppDir();
325
404
  const logFile = windowsLogFile();
326
405
  const errFile = path.join(appDir, "copilot-api.err");
327
- const launcherVbs = windowsLauncherVbsPath();
406
+ const launcherCmd = windowsLauncherCmdPath();
328
407
  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`,
408
+ const cmdContent = [
409
+ "@echo off",
410
+ `cd /d "%USERPROFILE%"`,
411
+ `${buildWindowsCommand(args)} >> "${logFile}" 2>> "${errFile}"`,
333
412
  ""
334
413
  ].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"
414
+ fs.writeFileSync(launcherCmd, cmdContent);
415
+ const psCmd = [
416
+ `$action = New-ScheduledTaskAction -Execute 'cmd.exe' -Argument '/c "${launcherCmd}"'`,
417
+ `$trigger = New-ScheduledTaskTrigger -AtLogOn -User $env:USERNAME`,
418
+ `$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit ([TimeSpan]::Zero)`,
419
+ `Register-ScheduledTask -TaskName '${WINDOWS_TASK_NAME}' -Action $action -Trigger $trigger -Settings $settings -Force`
420
+ ].join("; ");
421
+ const result = spawnSync("powershell", [
422
+ "-NoProfile",
423
+ "-Command",
424
+ psCmd
346
425
  ], {
347
426
  encoding: "utf-8",
348
- timeout: 1e4
427
+ timeout: 15e3
349
428
  });
350
429
  if (result.status !== 0) {
351
- console.error(`Failed to register Run key: ${(result.stderr || "").trim()}`);
430
+ console.error(`Failed to create scheduled task: ${(result.stderr || "").trim()}`);
352
431
  process.exit(1);
353
432
  }
354
- console.log(`Installed Windows Run key '${WINDOWS_RUN_KEY_NAME}'`);
355
- console.log(` Launcher: ${launcherVbs}`);
433
+ console.log(`Installed scheduled task '${WINDOWS_TASK_NAME}'`);
434
+ console.log(` Launcher: ${launcherCmd}`);
356
435
  console.log(` Log: ${logFile}`);
357
436
  console.log(` Mode: ${args.npx ? "npx (auto-update)" : "direct"}`);
358
437
  console.log(` Start: xc-copilot-api-daemon restart`);
359
438
  }
360
439
  function uninstallWindows() {
361
440
  stopWindows();
362
- spawnSync("reg.exe", [
363
- "delete",
364
- "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run",
365
- "/v",
366
- WINDOWS_RUN_KEY_NAME,
441
+ spawnSync("schtasks", [
442
+ "/delete",
443
+ "/tn",
444
+ WINDOWS_TASK_NAME,
367
445
  "/f"
368
446
  ], {
369
447
  encoding: "utf-8",
370
448
  timeout: 1e4
371
449
  });
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}'`);
450
+ const launcherCmd = windowsLauncherCmdPath();
451
+ if (fs.existsSync(launcherCmd)) fs.unlinkSync(launcherCmd);
452
+ console.log(`Uninstalled scheduled task '${WINDOWS_TASK_NAME}'`);
377
453
  }
378
454
  function startWindows() {
379
- const launcherVbs = windowsLauncherVbsPath();
380
- if (!fs.existsSync(launcherVbs)) {
381
- console.error(`Launcher not found: ${launcherVbs}`);
455
+ if (spawnSync("schtasks", [
456
+ "/run",
457
+ "/tn",
458
+ WINDOWS_TASK_NAME
459
+ ], {
460
+ encoding: "utf-8",
461
+ timeout: 1e4
462
+ }).status !== 0) {
463
+ console.error(`Scheduled task '${WINDOWS_TASK_NAME}' not found.`);
382
464
  console.error("Run 'xc-copilot-api-daemon install' first.");
383
465
  return false;
384
466
  }
385
- spawnSync("wscript.exe", [launcherVbs], {
386
- encoding: "utf-8",
387
- timeout: 1e4,
388
- detached: true,
389
- stdio: "ignore"
390
- });
391
467
  console.log("Started xc-copilot-api (background)");
392
468
  return true;
393
469
  }
@@ -430,23 +506,30 @@ function stopWindows() {
430
506
  return true;
431
507
  }
432
508
  function statusWindows() {
433
- if (spawnSync("reg.exe", [
434
- "query",
435
- "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run",
436
- "/v",
437
- WINDOWS_RUN_KEY_NAME
509
+ const taskResult = spawnSync("schtasks", [
510
+ "/query",
511
+ "/tn",
512
+ WINDOWS_TASK_NAME,
513
+ "/fo",
514
+ "LIST"
438
515
  ], {
439
516
  encoding: "utf-8",
440
517
  timeout: 5e3
441
- }).status !== 0) {
518
+ });
519
+ if (taskResult.status !== 0) {
442
520
  console.log("Daemon: not installed");
443
521
  return;
444
522
  }
445
523
  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()}`);
524
+ const statusMatch = (taskResult.stdout || "").match(/Status:\s*(.+)/i);
525
+ if (statusMatch) console.log(` Task: ${statusMatch[1].trim()}`);
526
+ const launcherCmd = windowsLauncherCmdPath();
527
+ if (fs.existsSync(launcherCmd)) {
528
+ const lines = fs.readFileSync(launcherCmd, "utf-8").split(/\r?\n/).filter((l) => l && !l.startsWith("@") && !l.startsWith("cd "));
529
+ if (lines.length > 0) {
530
+ const cmd = lines[0].replace(/\s*>>.*$/, "").trim();
531
+ if (cmd) console.log(` Command: ${cmd}`);
532
+ }
450
533
  }
451
534
  const pids = findCopilotPids("*copilot-api*start*");
452
535
  console.log(pids.length > 0 ? ` Status: running (PID ${pids.join(", ")})` : " Status: not running");
@@ -456,29 +539,32 @@ function statusWindows() {
456
539
  console.log(` Log: ${logFile} (${(stat.size / 1024).toFixed(1)} KB)`);
457
540
  }
458
541
  }
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
542
+
543
+ //#endregion
544
+ //#region src/daemon/index.ts
545
+ function findAllCopilotProcesses() {
546
+ if (isWindows()) {
547
+ const result = spawnSync("powershell", [
548
+ "-NoProfile",
549
+ "-Command",
550
+ `Get-CimInstance Win32_Process -Filter "CommandLine LIKE '%copilot-api%' AND ProcessId != $PID" | ForEach-Object { "$($_.ProcessId)|$($_.CommandLine)" }`
551
+ ], {
552
+ encoding: "utf-8",
553
+ timeout: 1e4
477
554
  });
555
+ if (result.status !== 0) return [];
556
+ const procs = [];
557
+ for (const line of result.stdout.trim().split("\n")) {
558
+ if (!line.trim()) continue;
559
+ const sep = line.indexOf("|");
560
+ if (sep < 0) continue;
561
+ procs.push({
562
+ pid: line.slice(0, sep).trim(),
563
+ command: line.slice(sep + 1).trim()
564
+ });
565
+ }
566
+ return procs;
478
567
  }
479
- return jobs;
480
- }
481
- function findAllCopilotProcesses() {
482
568
  try {
483
569
  const output = execSync("ps aux | grep -i copilot-api | grep -v grep", {
484
570
  encoding: "utf-8",
@@ -501,25 +587,6 @@ function findAllCopilotProcesses() {
501
587
  return [];
502
588
  }
503
589
  }
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
590
  function statusAll() {
524
591
  if (isMacOS()) {
525
592
  const jobs = findAllCopilotLaunchdJobs();
@@ -532,12 +599,8 @@ function statusAll() {
532
599
  console.log(` PID: ${job.pid ?? "not running"}`);
533
600
  }
534
601
  } 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 {
602
+ } else if (isWindows()) statusWindows();
603
+ else {
541
604
  const units = findAllCopilotSystemdUnits();
542
605
  if (units.length > 0) {
543
606
  console.log("=== Systemd Units ===");
@@ -555,11 +618,11 @@ function statusAll() {
555
618
  console.log(r.stdout.trim());
556
619
  }
557
620
  } 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
- }
621
+ }
622
+ const procs = findAllCopilotProcesses();
623
+ if (procs.length > 0) {
624
+ console.log("\n=== Processes ===");
625
+ for (const proc of procs) console.log(` PID ${proc.pid}: ${proc.command}`);
563
626
  }
564
627
  }
565
628
  function stopAll() {
@@ -576,7 +639,7 @@ function stopAll() {
576
639
  });
577
640
  stopped++;
578
641
  }
579
- } else {
642
+ } else if (!isWindows()) {
580
643
  const units = findAllCopilotSystemdUnits();
581
644
  for (const unit of units) {
582
645
  console.log(`Stopping systemd unit '${unit}'...`);
@@ -594,7 +657,17 @@ function stopAll() {
594
657
  const procs = findAllCopilotProcesses();
595
658
  for (const proc of procs) {
596
659
  console.log(`Killing PID ${proc.pid}: ${proc.command}`);
597
- try {
660
+ if (isWindows()) {
661
+ spawnSync("taskkill", [
662
+ "/PID",
663
+ proc.pid,
664
+ "/F"
665
+ ], {
666
+ encoding: "utf-8",
667
+ timeout: 5e3
668
+ });
669
+ stopped++;
670
+ } else try {
598
671
  process.kill(Number.parseInt(proc.pid, 10), "SIGTERM");
599
672
  stopped++;
600
673
  } catch {}
@@ -602,25 +675,10 @@ function stopAll() {
602
675
  if (stopped === 0) console.log("No copilot-api processes found.");
603
676
  else console.log(`\nStopped/killed ${stopped} item(s).`);
604
677
  }
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
678
  const installCmd = defineCommand({
621
679
  meta: {
622
680
  name: "install",
623
- description: "Install xc-copilot-api daemon (launchd on macOS, systemd on Linux, Run key on Windows)"
681
+ description: "Install xc-copilot-api daemon (launchd on macOS, systemd on Linux, Task Scheduler on Windows)"
624
682
  },
625
683
  args: {
626
684
  npx: {