@jer-y/copilot-proxy 0.2.0 → 0.2.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.
package/dist/main.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { PATHS, ensurePaths } from "./paths-CA6OZ0WA.js";
3
3
  import { loadDaemonConfig } from "./config-D1kMGXKU.js";
4
- import { isDaemonRunning, isProcessRunning, readPid, removePidFile } from "./pid-uKNpN4v-.js";
5
- import { daemonStart } from "./start-Dcv2sypx.js";
4
+ import { isDaemonRunning, isProcessRunning, readPid, removePidFile } from "./pid-BQbC5Hf2.js";
5
+ import { daemonStart } from "./start-BwtzhMKZ.js";
6
6
  import { defineCommand, runMain } from "citty";
7
7
  import consola from "consola";
8
8
  import fs from "node:fs/promises";
@@ -389,7 +389,7 @@ const disable = defineCommand({
389
389
  const { uninstallAutoStart } = await import("./darwin-BVmd1DeO.js");
390
390
  success = await uninstallAutoStart();
391
391
  } else if (platform === "win32") {
392
- const { uninstallAutoStart } = await import("./win32-D1-MlKl7.js");
392
+ const { uninstallAutoStart } = await import("./win32-DAStPxcF.js");
393
393
  success = await uninstallAutoStart();
394
394
  } else {
395
395
  consola.error(`Unsupported platform: ${platform}`);
@@ -426,7 +426,7 @@ const enable = defineCommand({
426
426
  const { installAutoStart } = await import("./darwin-BVmd1DeO.js");
427
427
  success = await installAutoStart(execPath, args);
428
428
  } else if (platform === "win32") {
429
- const { installAutoStart } = await import("./win32-D1-MlKl7.js");
429
+ const { installAutoStart } = await import("./win32-DAStPxcF.js");
430
430
  success = await installAutoStart(execPath, args);
431
431
  } else {
432
432
  consola.error(`Unsupported platform: ${platform}`);
@@ -1974,7 +1974,7 @@ const start = defineCommand({
1974
1974
  consola.error("Supervisor mode: daemon config not found");
1975
1975
  process.exit(1);
1976
1976
  }
1977
- const { runAsSupervisor } = await import("./supervisor-BbH28zwT.js");
1977
+ const { runAsSupervisor } = await import("./supervisor-C2pujTsK.js");
1978
1978
  const options = {
1979
1979
  port: config.port,
1980
1980
  verbose: config.verbose,
@@ -1994,7 +1994,7 @@ const start = defineCommand({
1994
1994
  consola.error("Cannot use --claude-code with --daemon (interactive mode)");
1995
1995
  process.exit(1);
1996
1996
  }
1997
- const { daemonStart: daemonStart$1 } = await import("./start-C-0OcnXB.js");
1997
+ const { daemonStart: daemonStart$1 } = await import("./start-CELRn1-3.js");
1998
1998
  daemonStart$1({
1999
1999
  port,
2000
2000
  verbose: args.verbose,
@@ -4,10 +4,10 @@ import fs from "node:fs";
4
4
  import { execSync } from "node:child_process";
5
5
 
6
6
  //#region src/daemon/pid.ts
7
- function writePid(pid) {
7
+ function writePid(pid, startTime) {
8
8
  fs.mkdirSync(PATHS.APP_DIR, { recursive: true });
9
9
  const pidPath = PATHS.DAEMON_PID;
10
- fs.writeFileSync(pidPath, `${pid}\n${Date.now()}`, { mode: 384 });
10
+ fs.writeFileSync(pidPath, `${pid}\n${startTime ?? Date.now()}`, { mode: 384 });
11
11
  try {
12
12
  fs.chmodSync(pidPath, 384);
13
13
  } catch {}
@@ -107,7 +107,11 @@ function getProcessStartTime(pid) {
107
107
  } catch {
108
108
  const output = execSync(`ps -p ${pid} -o lstart=`, {
109
109
  stdio: "pipe",
110
- encoding: "utf8"
110
+ encoding: "utf8",
111
+ env: {
112
+ ...process.env,
113
+ LC_ALL: "C"
114
+ }
111
115
  }).trim();
112
116
  if (!output) return null;
113
117
  const parsed = Date.parse(output);
@@ -135,4 +139,4 @@ function isDaemonRunning() {
135
139
 
136
140
  //#endregion
137
141
  export { isDaemonRunning, isProcessRunning, readPid, removePidFile, writePid };
138
- //# sourceMappingURL=pid-uKNpN4v-.js.map
142
+ //# sourceMappingURL=pid-BQbC5Hf2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pid-BQbC5Hf2.js","names":["pid","error: unknown","cmdline: string"],"sources":["../src/daemon/pid.ts"],"sourcesContent":["import { execSync } from 'node:child_process'\nimport fs from 'node:fs'\nimport process from 'node:process'\n\nimport { PATHS } from '~/lib/paths'\n\nexport interface DaemonPidInfo {\n pid: number\n startTime: number\n}\n\nexport function writePid(pid: number, startTime?: number): void {\n fs.mkdirSync(PATHS.APP_DIR, { recursive: true })\n const pidPath = PATHS.DAEMON_PID\n fs.writeFileSync(pidPath, `${pid}\\n${startTime ?? Date.now()}`, { mode: 0o600 })\n // Ensure permissions are correct even if file already existed with wider perms\n try {\n fs.chmodSync(pidPath, 0o600)\n }\n catch {}\n}\n\nexport function readPid(): DaemonPidInfo | null {\n try {\n const content = fs.readFileSync(PATHS.DAEMON_PID, 'utf8').trim()\n const lines = content.split('\\n')\n if (lines.length < 2) {\n // Legacy format: just PID\n const pid = Number.parseInt(lines[0], 10)\n if (Number.isNaN(pid) || pid <= 0 || String(pid) !== lines[0])\n return null\n return { pid, startTime: 0 }\n }\n const pid = Number.parseInt(lines[0], 10)\n const startTime = Number.parseInt(lines[1], 10)\n if (Number.isNaN(pid) || pid <= 0 || String(pid) !== lines[0])\n return null\n if (Number.isNaN(startTime))\n return null\n return { pid, startTime }\n }\n catch {\n return null\n }\n}\n\nexport function removePidFile(): void {\n try {\n fs.unlinkSync(PATHS.DAEMON_PID)\n }\n catch {}\n}\n\nexport function isProcessRunning(pid: number): boolean {\n try {\n process.kill(pid, 0)\n return true\n }\n catch (error: unknown) {\n if (error instanceof Error && 'code' in error && error.code === 'EPERM') {\n return true\n }\n return false\n }\n}\n\n/**\n * Check if a running process is actually our daemon supervisor.\n * Uses two checks:\n * 1. Command line must contain '_supervisor' flag\n * 2. Process start time must match the recorded startTime in PID file\n * (within tolerance, to prevent spoofing with a fake process)\n */\nfunction isOurDaemonProcess(pid: number, recordedStartTime: number): boolean {\n try {\n // Check command line\n let cmdline: string\n if (process.platform === 'win32') {\n cmdline = execSync(\n `wmic process where ProcessId=${pid} get CommandLine /format:list`,\n { stdio: 'pipe', encoding: 'utf8' },\n )\n }\n else {\n try {\n cmdline = fs.readFileSync(`/proc/${pid}/cmdline`, 'utf8')\n }\n catch {\n cmdline = execSync(`ps -p ${pid} -o command=`, {\n stdio: 'pipe',\n encoding: 'utf8',\n })\n }\n }\n\n if (!cmdline.includes('_supervisor'))\n return false\n\n // If no recorded start time (legacy), command line check alone is sufficient\n if (recordedStartTime === 0)\n return true\n\n // Check process start time matches what we recorded\n const processStartMs = getProcessStartTime(pid)\n if (processStartMs === null)\n return false\n\n // Allow 5s tolerance between when we recorded the time and when the OS recorded process start\n return Math.abs(processStartMs - recordedStartTime) < 5000\n }\n catch {\n return false\n }\n}\n\n/**\n * Get the start time of a process in milliseconds since epoch.\n * Returns null if unavailable.\n */\nfunction getProcessStartTime(pid: number): number | null {\n try {\n if (process.platform === 'win32') {\n const output = execSync(\n `wmic process where ProcessId=${pid} get CreationDate /format:list`,\n { stdio: 'pipe', encoding: 'utf8' },\n )\n // Format: CreationDate=20260302123456.123456+480 (offset is minutes from UTC)\n const match = output.match(/CreationDate=(\\d{4})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})\\.\\d+([+-]\\d+)/)\n if (!match)\n return null\n const [, y, m, d, h, min, s, offsetStr] = match\n const offsetMinutes = Number.parseInt(offsetStr, 10)\n // Parse as local time then adjust by the UTC offset\n const localMs = new Date(`${y}-${m}-${d}T${h}:${min}:${s}Z`).getTime()\n return localMs - offsetMinutes * 60 * 1000\n }\n else {\n // Linux: read /proc/<pid>/stat, field 22 is starttime in clock ticks since boot\n try {\n const stat = fs.readFileSync(`/proc/${pid}/stat`, 'utf8')\n // Fields are space-separated, but field 2 (comm) can contain spaces/parens\n // Find the last ')' to skip past comm field\n const afterComm = stat.substring(stat.lastIndexOf(')') + 2)\n const fields = afterComm.split(' ')\n // starttime is field 22 overall, which is field 20 after comm (0-indexed: index 19)\n const startTicks = Number.parseInt(fields[19], 10)\n if (Number.isNaN(startTicks))\n return null\n\n // Get system boot time and clock ticks per second\n const uptimeStr = fs.readFileSync('/proc/uptime', 'utf8')\n const uptimeSec = Number.parseFloat(uptimeStr.split(' ')[0])\n // CLK_TCK is typically 100 on Linux\n const clkTck = 100\n const processUptimeSec = startTicks / clkTck\n const bootTimeMs = Date.now() - uptimeSec * 1000\n return bootTimeMs + processUptimeSec * 1000\n }\n catch {\n // macOS: use ps -p <pid> -o lstart=\n const output = execSync(`ps -p ${pid} -o lstart=`, {\n stdio: 'pipe',\n encoding: 'utf8',\n env: { ...process.env, LC_ALL: 'C' },\n }).trim()\n if (!output)\n return null\n const parsed = Date.parse(output)\n return Number.isNaN(parsed) ? null : parsed\n }\n }\n }\n catch {\n return null\n }\n}\n\n/**\n * Check if the PID from the PID file is likely still our daemon process.\n * Verifies that the process is alive AND that its command line contains\n * the _supervisor flag AND that its start time matches what we recorded.\n */\nexport function isDaemonRunning(): { running: true, pid: number } | { running: false } {\n const info = readPid()\n if (info === null)\n return { running: false }\n if (!isProcessRunning(info.pid))\n return { running: false }\n\n // Verify the process is actually our daemon\n if (!isOurDaemonProcess(info.pid, info.startTime))\n return { running: false }\n\n return { running: true, pid: info.pid }\n}\n"],"mappings":";;;;;;AAWA,SAAgB,SAAS,KAAa,WAA0B;AAC9D,IAAG,UAAU,MAAM,SAAS,EAAE,WAAW,MAAM,CAAC;CAChD,MAAM,UAAU,MAAM;AACtB,IAAG,cAAc,SAAS,GAAG,IAAI,IAAI,aAAa,KAAK,KAAK,IAAI,EAAE,MAAM,KAAO,CAAC;AAEhF,KAAI;AACF,KAAG,UAAU,SAAS,IAAM;SAExB;;AAGR,SAAgB,UAAgC;AAC9C,KAAI;EAEF,MAAM,QADU,GAAG,aAAa,MAAM,YAAY,OAAO,CAAC,MAAM,CAC1C,MAAM,KAAK;AACjC,MAAI,MAAM,SAAS,GAAG;GAEpB,MAAMA,QAAM,OAAO,SAAS,MAAM,IAAI,GAAG;AACzC,OAAI,OAAO,MAAMA,MAAI,IAAIA,SAAO,KAAK,OAAOA,MAAI,KAAK,MAAM,GACzD,QAAO;AACT,UAAO;IAAE;IAAK,WAAW;IAAG;;EAE9B,MAAM,MAAM,OAAO,SAAS,MAAM,IAAI,GAAG;EACzC,MAAM,YAAY,OAAO,SAAS,MAAM,IAAI,GAAG;AAC/C,MAAI,OAAO,MAAM,IAAI,IAAI,OAAO,KAAK,OAAO,IAAI,KAAK,MAAM,GACzD,QAAO;AACT,MAAI,OAAO,MAAM,UAAU,CACzB,QAAO;AACT,SAAO;GAAE;GAAK;GAAW;SAErB;AACJ,SAAO;;;AAIX,SAAgB,gBAAsB;AACpC,KAAI;AACF,KAAG,WAAW,MAAM,WAAW;SAE3B;;AAGR,SAAgB,iBAAiB,KAAsB;AACrD,KAAI;AACF,UAAQ,KAAK,KAAK,EAAE;AACpB,SAAO;UAEFC,OAAgB;AACrB,MAAI,iBAAiB,SAAS,UAAU,SAAS,MAAM,SAAS,QAC9D,QAAO;AAET,SAAO;;;;;;;;;;AAWX,SAAS,mBAAmB,KAAa,mBAAoC;AAC3E,KAAI;EAEF,IAAIC;AACJ,MAAI,QAAQ,aAAa,QACvB,WAAU,SACR,gCAAgC,IAAI,gCACpC;GAAE,OAAO;GAAQ,UAAU;GAAQ,CACpC;MAGD,KAAI;AACF,aAAU,GAAG,aAAa,SAAS,IAAI,WAAW,OAAO;UAErD;AACJ,aAAU,SAAS,SAAS,IAAI,eAAe;IAC7C,OAAO;IACP,UAAU;IACX,CAAC;;AAIN,MAAI,CAAC,QAAQ,SAAS,cAAc,CAClC,QAAO;AAGT,MAAI,sBAAsB,EACxB,QAAO;EAGT,MAAM,iBAAiB,oBAAoB,IAAI;AAC/C,MAAI,mBAAmB,KACrB,QAAO;AAGT,SAAO,KAAK,IAAI,iBAAiB,kBAAkB,GAAG;SAElD;AACJ,SAAO;;;;;;;AAQX,SAAS,oBAAoB,KAA4B;AACvD,KAAI;AACF,MAAI,QAAQ,aAAa,SAAS;GAMhC,MAAM,QALS,SACb,gCAAgC,IAAI,iCACpC;IAAE,OAAO;IAAQ,UAAU;IAAQ,CACpC,CAEoB,MAAM,wEAAwE;AACnG,OAAI,CAAC,MACH,QAAO;GACT,MAAM,GAAG,GAAG,GAAG,GAAG,GAAG,KAAK,GAAG,aAAa;GAC1C,MAAM,gBAAgB,OAAO,SAAS,WAAW,GAAG;AAGpD,2BADgB,IAAI,KAAK,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,GAAG,EAAE,GAAG,EAAC,SAAS,GACrD,gBAAgB,KAAK;QAItC,KAAI;GACF,MAAM,OAAO,GAAG,aAAa,SAAS,IAAI,QAAQ,OAAO;GAIzD,MAAM,SADY,KAAK,UAAU,KAAK,YAAY,IAAI,GAAG,EAAE,CAClC,MAAM,IAAI;GAEnC,MAAM,aAAa,OAAO,SAAS,OAAO,KAAK,GAAG;AAClD,OAAI,OAAO,MAAM,WAAW,CAC1B,QAAO;GAGT,MAAM,YAAY,GAAG,aAAa,gBAAgB,OAAO;GACzD,MAAM,YAAY,OAAO,WAAW,UAAU,MAAM,IAAI,CAAC,GAAG;GAG5D,MAAM,mBAAmB,aADV;AAGf,UADmB,KAAK,KAAK,GAAG,YAAY,MACxB,mBAAmB;UAEnC;GAEJ,MAAM,SAAS,SAAS,SAAS,IAAI,cAAc;IACjD,OAAO;IACP,UAAU;IACV,KAAK;KAAE,GAAG,QAAQ;KAAK,QAAQ;KAAK;IACrC,CAAC,CAAC,MAAM;AACT,OAAI,CAAC,OACH,QAAO;GACT,MAAM,SAAS,KAAK,MAAM,OAAO;AACjC,UAAO,OAAO,MAAM,OAAO,GAAG,OAAO;;SAIrC;AACJ,SAAO;;;;;;;;AASX,SAAgB,kBAAuE;CACrF,MAAM,OAAO,SAAS;AACtB,KAAI,SAAS,KACX,QAAO,EAAE,SAAS,OAAO;AAC3B,KAAI,CAAC,iBAAiB,KAAK,IAAI,CAC7B,QAAO,EAAE,SAAS,OAAO;AAG3B,KAAI,CAAC,mBAAmB,KAAK,KAAK,KAAK,UAAU,CAC/C,QAAO,EAAE,SAAS,OAAO;AAE3B,QAAO;EAAE,SAAS;EAAM,KAAK,KAAK;EAAK"}
@@ -1,6 +1,6 @@
1
1
  import { PATHS } from "./paths-CA6OZ0WA.js";
2
2
  import { saveDaemonConfig } from "./config-D1kMGXKU.js";
3
- import { isDaemonRunning, removePidFile, writePid } from "./pid-uKNpN4v-.js";
3
+ import { isDaemonRunning, removePidFile, writePid } from "./pid-BQbC5Hf2.js";
4
4
  import consola from "consola";
5
5
  import process from "node:process";
6
6
  import fs from "node:fs";
@@ -104,4 +104,4 @@ function daemonStart(config) {
104
104
 
105
105
  //#endregion
106
106
  export { daemonStart };
107
- //# sourceMappingURL=start-Dcv2sypx.js.map
107
+ //# sourceMappingURL=start-BwtzhMKZ.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"start-Dcv2sypx.js","names":[],"sources":["../src/daemon/start.ts"],"sourcesContent":["import type { DaemonConfig } from '~/daemon/config'\nimport { spawn } from 'node:child_process'\nimport fs from 'node:fs'\nimport process from 'node:process'\n\nimport consola from 'consola'\nimport { saveDaemonConfig } from '~/daemon/config'\nimport { isDaemonRunning, removePidFile, writePid } from '~/daemon/pid'\nimport { PATHS } from '~/lib/paths'\n\nconst LOCK_PATH = `${PATHS.DAEMON_PID}.lock`\n\nfunction acquireLock(): boolean {\n try {\n fs.mkdirSync(PATHS.APP_DIR, { recursive: true })\n // O_CREAT | O_EXCL — fails if file already exists (atomic)\n const fd = fs.openSync(LOCK_PATH, 'wx')\n fs.writeSync(fd, String(process.pid))\n fs.closeSync(fd)\n return true\n }\n catch {\n return false\n }\n}\n\nfunction releaseLock(): void {\n try {\n fs.unlinkSync(LOCK_PATH)\n }\n catch {}\n}\n\nfunction ensureLock(): void {\n if (acquireLock())\n return\n\n // Check if the lock is stale (owner process dead)\n try {\n const lockPid = Number.parseInt(fs.readFileSync(LOCK_PATH, 'utf8').trim(), 10)\n if (!Number.isNaN(lockPid) && lockPid > 0) {\n try {\n process.kill(lockPid, 0)\n // Lock holder is alive — genuine concurrent start\n consola.error('Another start operation is in progress')\n process.exit(1)\n }\n catch {\n // Lock holder is dead — stale lock, remove and retry\n releaseLock()\n if (!acquireLock()) {\n consola.error('Failed to acquire start lock')\n process.exit(1)\n }\n }\n }\n else {\n releaseLock()\n if (!acquireLock()) {\n consola.error('Failed to acquire start lock')\n process.exit(1)\n }\n }\n }\n catch {\n consola.error('Failed to acquire start lock')\n process.exit(1)\n }\n}\n\nexport function daemonStart(config: DaemonConfig): void {\n // Acquire lock to prevent concurrent starts.\n // ensureLock() calls process.exit() before lock is held,\n // so no cleanup needed in that path.\n ensureLock()\n\n // From here on, we hold the lock. Always release before exiting.\n const exitWithLock = (code: number): never => {\n releaseLock()\n process.exit(code)\n throw new Error('unreachable')\n }\n\n // Check if already running\n const daemon = isDaemonRunning()\n if (daemon.running) {\n consola.error(`Daemon is already running (PID: ${daemon.pid})`)\n exitWithLock(1)\n }\n\n // Save config for restart/enable\n saveDaemonConfig(config)\n\n // If a github token was provided, persist it to the token file\n // so the supervisor can use it (we don't store tokens in daemon.json)\n if (config.githubToken) {\n fs.mkdirSync(PATHS.APP_DIR, { recursive: true })\n fs.writeFileSync(PATHS.GITHUB_TOKEN_PATH, config.githubToken, { mode: 0o600 })\n try {\n fs.chmodSync(PATHS.GITHUB_TOKEN_PATH, 0o600)\n }\n catch {}\n }\n\n // Resolve the executable path\n const execPath = process.argv[0]\n const scriptPath = process.argv[1]\n\n const logStream = fs.openSync(PATHS.DAEMON_LOG, 'a', 0o600)\n // Ensure permissions are correct even if file already existed with wider perms\n try {\n fs.fchmodSync(logStream, 0o600)\n }\n catch {}\n\n const child = spawn(execPath, [scriptPath, 'start', '--_supervisor'], {\n detached: true,\n stdio: ['ignore', logStream, logStream],\n env: process.env,\n })\n\n if (child.pid === undefined) {\n consola.error('Failed to start daemon process')\n removePidFile()\n return exitWithLock(1)\n }\n\n writePid(child.pid)\n child.unref()\n\n consola.success(`Daemon started (PID: ${child.pid})`)\n consola.info(`Logs: ${PATHS.DAEMON_LOG}`)\n exitWithLock(0)\n}\n"],"mappings":";;;;;;;;;AAUA,MAAM,YAAY,GAAG,MAAM,WAAW;AAEtC,SAAS,cAAuB;AAC9B,KAAI;AACF,KAAG,UAAU,MAAM,SAAS,EAAE,WAAW,MAAM,CAAC;EAEhD,MAAM,KAAK,GAAG,SAAS,WAAW,KAAK;AACvC,KAAG,UAAU,IAAI,OAAO,QAAQ,IAAI,CAAC;AACrC,KAAG,UAAU,GAAG;AAChB,SAAO;SAEH;AACJ,SAAO;;;AAIX,SAAS,cAAoB;AAC3B,KAAI;AACF,KAAG,WAAW,UAAU;SAEpB;;AAGR,SAAS,aAAmB;AAC1B,KAAI,aAAa,CACf;AAGF,KAAI;EACF,MAAM,UAAU,OAAO,SAAS,GAAG,aAAa,WAAW,OAAO,CAAC,MAAM,EAAE,GAAG;AAC9E,MAAI,CAAC,OAAO,MAAM,QAAQ,IAAI,UAAU,EACtC,KAAI;AACF,WAAQ,KAAK,SAAS,EAAE;AAExB,WAAQ,MAAM,yCAAyC;AACvD,WAAQ,KAAK,EAAE;UAEX;AAEJ,gBAAa;AACb,OAAI,CAAC,aAAa,EAAE;AAClB,YAAQ,MAAM,+BAA+B;AAC7C,YAAQ,KAAK,EAAE;;;OAIhB;AACH,gBAAa;AACb,OAAI,CAAC,aAAa,EAAE;AAClB,YAAQ,MAAM,+BAA+B;AAC7C,YAAQ,KAAK,EAAE;;;SAIf;AACJ,UAAQ,MAAM,+BAA+B;AAC7C,UAAQ,KAAK,EAAE;;;AAInB,SAAgB,YAAY,QAA4B;AAItD,aAAY;CAGZ,MAAM,gBAAgB,SAAwB;AAC5C,eAAa;AACb,UAAQ,KAAK,KAAK;AAClB,QAAM,IAAI,MAAM,cAAc;;CAIhC,MAAM,SAAS,iBAAiB;AAChC,KAAI,OAAO,SAAS;AAClB,UAAQ,MAAM,mCAAmC,OAAO,IAAI,GAAG;AAC/D,eAAa,EAAE;;AAIjB,kBAAiB,OAAO;AAIxB,KAAI,OAAO,aAAa;AACtB,KAAG,UAAU,MAAM,SAAS,EAAE,WAAW,MAAM,CAAC;AAChD,KAAG,cAAc,MAAM,mBAAmB,OAAO,aAAa,EAAE,MAAM,KAAO,CAAC;AAC9E,MAAI;AACF,MAAG,UAAU,MAAM,mBAAmB,IAAM;UAExC;;CAIR,MAAM,WAAW,QAAQ,KAAK;CAC9B,MAAM,aAAa,QAAQ,KAAK;CAEhC,MAAM,YAAY,GAAG,SAAS,MAAM,YAAY,KAAK,IAAM;AAE3D,KAAI;AACF,KAAG,WAAW,WAAW,IAAM;SAE3B;CAEN,MAAM,QAAQ,MAAM,UAAU;EAAC;EAAY;EAAS;EAAgB,EAAE;EACpE,UAAU;EACV,OAAO;GAAC;GAAU;GAAW;GAAU;EACvC,KAAK,QAAQ;EACd,CAAC;AAEF,KAAI,MAAM,QAAQ,QAAW;AAC3B,UAAQ,MAAM,iCAAiC;AAC/C,iBAAe;AACf,SAAO,aAAa,EAAE;;AAGxB,UAAS,MAAM,IAAI;AACnB,OAAM,OAAO;AAEb,SAAQ,QAAQ,wBAAwB,MAAM,IAAI,GAAG;AACrD,SAAQ,KAAK,SAAS,MAAM,aAAa;AACzC,cAAa,EAAE"}
1
+ {"version":3,"file":"start-BwtzhMKZ.js","names":[],"sources":["../src/daemon/start.ts"],"sourcesContent":["import type { DaemonConfig } from '~/daemon/config'\nimport { spawn } from 'node:child_process'\nimport fs from 'node:fs'\nimport process from 'node:process'\n\nimport consola from 'consola'\nimport { saveDaemonConfig } from '~/daemon/config'\nimport { isDaemonRunning, removePidFile, writePid } from '~/daemon/pid'\nimport { PATHS } from '~/lib/paths'\n\nconst LOCK_PATH = `${PATHS.DAEMON_PID}.lock`\n\nfunction acquireLock(): boolean {\n try {\n fs.mkdirSync(PATHS.APP_DIR, { recursive: true })\n // O_CREAT | O_EXCL — fails if file already exists (atomic)\n const fd = fs.openSync(LOCK_PATH, 'wx')\n fs.writeSync(fd, String(process.pid))\n fs.closeSync(fd)\n return true\n }\n catch {\n return false\n }\n}\n\nfunction releaseLock(): void {\n try {\n fs.unlinkSync(LOCK_PATH)\n }\n catch {}\n}\n\nfunction ensureLock(): void {\n if (acquireLock())\n return\n\n // Check if the lock is stale (owner process dead)\n try {\n const lockPid = Number.parseInt(fs.readFileSync(LOCK_PATH, 'utf8').trim(), 10)\n if (!Number.isNaN(lockPid) && lockPid > 0) {\n try {\n process.kill(lockPid, 0)\n // Lock holder is alive — genuine concurrent start\n consola.error('Another start operation is in progress')\n process.exit(1)\n }\n catch {\n // Lock holder is dead — stale lock, remove and retry\n releaseLock()\n if (!acquireLock()) {\n consola.error('Failed to acquire start lock')\n process.exit(1)\n }\n }\n }\n else {\n releaseLock()\n if (!acquireLock()) {\n consola.error('Failed to acquire start lock')\n process.exit(1)\n }\n }\n }\n catch {\n consola.error('Failed to acquire start lock')\n process.exit(1)\n }\n}\n\nexport function daemonStart(config: DaemonConfig): void {\n // Acquire lock to prevent concurrent starts.\n // ensureLock() calls process.exit() before lock is held,\n // so no cleanup needed in that path.\n ensureLock()\n\n // From here on, we hold the lock. Always release before exiting.\n const exitWithLock = (code: number): never => {\n releaseLock()\n process.exit(code)\n throw new Error('unreachable')\n }\n\n // Check if already running\n const daemon = isDaemonRunning()\n if (daemon.running) {\n consola.error(`Daemon is already running (PID: ${daemon.pid})`)\n exitWithLock(1)\n }\n\n // Save config for restart/enable\n saveDaemonConfig(config)\n\n // If a github token was provided, persist it to the token file\n // so the supervisor can use it (we don't store tokens in daemon.json)\n if (config.githubToken) {\n fs.mkdirSync(PATHS.APP_DIR, { recursive: true })\n fs.writeFileSync(PATHS.GITHUB_TOKEN_PATH, config.githubToken, { mode: 0o600 })\n try {\n fs.chmodSync(PATHS.GITHUB_TOKEN_PATH, 0o600)\n }\n catch {}\n }\n\n // Resolve the executable path\n const execPath = process.argv[0]\n const scriptPath = process.argv[1]\n\n const logStream = fs.openSync(PATHS.DAEMON_LOG, 'a', 0o600)\n // Ensure permissions are correct even if file already existed with wider perms\n try {\n fs.fchmodSync(logStream, 0o600)\n }\n catch {}\n\n const child = spawn(execPath, [scriptPath, 'start', '--_supervisor'], {\n detached: true,\n stdio: ['ignore', logStream, logStream],\n env: process.env,\n })\n\n if (child.pid === undefined) {\n consola.error('Failed to start daemon process')\n removePidFile()\n return exitWithLock(1)\n }\n\n writePid(child.pid)\n child.unref()\n\n consola.success(`Daemon started (PID: ${child.pid})`)\n consola.info(`Logs: ${PATHS.DAEMON_LOG}`)\n exitWithLock(0)\n}\n"],"mappings":";;;;;;;;;AAUA,MAAM,YAAY,GAAG,MAAM,WAAW;AAEtC,SAAS,cAAuB;AAC9B,KAAI;AACF,KAAG,UAAU,MAAM,SAAS,EAAE,WAAW,MAAM,CAAC;EAEhD,MAAM,KAAK,GAAG,SAAS,WAAW,KAAK;AACvC,KAAG,UAAU,IAAI,OAAO,QAAQ,IAAI,CAAC;AACrC,KAAG,UAAU,GAAG;AAChB,SAAO;SAEH;AACJ,SAAO;;;AAIX,SAAS,cAAoB;AAC3B,KAAI;AACF,KAAG,WAAW,UAAU;SAEpB;;AAGR,SAAS,aAAmB;AAC1B,KAAI,aAAa,CACf;AAGF,KAAI;EACF,MAAM,UAAU,OAAO,SAAS,GAAG,aAAa,WAAW,OAAO,CAAC,MAAM,EAAE,GAAG;AAC9E,MAAI,CAAC,OAAO,MAAM,QAAQ,IAAI,UAAU,EACtC,KAAI;AACF,WAAQ,KAAK,SAAS,EAAE;AAExB,WAAQ,MAAM,yCAAyC;AACvD,WAAQ,KAAK,EAAE;UAEX;AAEJ,gBAAa;AACb,OAAI,CAAC,aAAa,EAAE;AAClB,YAAQ,MAAM,+BAA+B;AAC7C,YAAQ,KAAK,EAAE;;;OAIhB;AACH,gBAAa;AACb,OAAI,CAAC,aAAa,EAAE;AAClB,YAAQ,MAAM,+BAA+B;AAC7C,YAAQ,KAAK,EAAE;;;SAIf;AACJ,UAAQ,MAAM,+BAA+B;AAC7C,UAAQ,KAAK,EAAE;;;AAInB,SAAgB,YAAY,QAA4B;AAItD,aAAY;CAGZ,MAAM,gBAAgB,SAAwB;AAC5C,eAAa;AACb,UAAQ,KAAK,KAAK;AAClB,QAAM,IAAI,MAAM,cAAc;;CAIhC,MAAM,SAAS,iBAAiB;AAChC,KAAI,OAAO,SAAS;AAClB,UAAQ,MAAM,mCAAmC,OAAO,IAAI,GAAG;AAC/D,eAAa,EAAE;;AAIjB,kBAAiB,OAAO;AAIxB,KAAI,OAAO,aAAa;AACtB,KAAG,UAAU,MAAM,SAAS,EAAE,WAAW,MAAM,CAAC;AAChD,KAAG,cAAc,MAAM,mBAAmB,OAAO,aAAa,EAAE,MAAM,KAAO,CAAC;AAC9E,MAAI;AACF,MAAG,UAAU,MAAM,mBAAmB,IAAM;UAExC;;CAIR,MAAM,WAAW,QAAQ,KAAK;CAC9B,MAAM,aAAa,QAAQ,KAAK;CAEhC,MAAM,YAAY,GAAG,SAAS,MAAM,YAAY,KAAK,IAAM;AAE3D,KAAI;AACF,KAAG,WAAW,WAAW,IAAM;SAE3B;CAEN,MAAM,QAAQ,MAAM,UAAU;EAAC;EAAY;EAAS;EAAgB,EAAE;EACpE,UAAU;EACV,OAAO;GAAC;GAAU;GAAW;GAAU;EACvC,KAAK,QAAQ;EACd,CAAC;AAEF,KAAI,MAAM,QAAQ,QAAW;AAC3B,UAAQ,MAAM,iCAAiC;AAC/C,iBAAe;AACf,SAAO,aAAa,EAAE;;AAGxB,UAAS,MAAM,IAAI;AACnB,OAAM,OAAO;AAEb,SAAQ,QAAQ,wBAAwB,MAAM,IAAI,GAAG;AACrD,SAAQ,KAAK,SAAS,MAAM,aAAa;AACzC,cAAa,EAAE"}
@@ -0,0 +1,6 @@
1
+ import "./paths-CA6OZ0WA.js";
2
+ import "./config-D1kMGXKU.js";
3
+ import "./pid-BQbC5Hf2.js";
4
+ import { daemonStart } from "./start-BwtzhMKZ.js";
5
+
6
+ export { daemonStart };
@@ -1,5 +1,5 @@
1
1
  import "./paths-CA6OZ0WA.js";
2
- import { removePidFile } from "./pid-uKNpN4v-.js";
2
+ import { readPid, removePidFile, writePid } from "./pid-BQbC5Hf2.js";
3
3
  import consola from "consola";
4
4
  import process from "node:process";
5
5
 
@@ -9,6 +9,8 @@ const STABLE_THRESHOLD_MS = 6e4;
9
9
  async function runAsSupervisor(runFn) {
10
10
  let backoffMs = 1e3;
11
11
  let lastStartTime = Date.now();
12
+ const supervisorStartTime = Date.now();
13
+ writePid(process.pid, supervisorStartTime);
12
14
  const cleanup = () => {
13
15
  removePidFile();
14
16
  process.exit(0);
@@ -19,6 +21,8 @@ async function runAsSupervisor(runFn) {
19
21
  removePidFile();
20
22
  });
21
23
  while (true) {
24
+ const existing = readPid();
25
+ if (existing === null || existing.pid !== process.pid) writePid(process.pid, supervisorStartTime);
22
26
  lastStartTime = Date.now();
23
27
  try {
24
28
  await runFn();
@@ -36,4 +40,4 @@ async function runAsSupervisor(runFn) {
36
40
 
37
41
  //#endregion
38
42
  export { runAsSupervisor };
39
- //# sourceMappingURL=supervisor-BbH28zwT.js.map
43
+ //# sourceMappingURL=supervisor-C2pujTsK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supervisor-C2pujTsK.js","names":[],"sources":["../src/daemon/supervisor.ts"],"sourcesContent":["import process from 'node:process'\nimport consola from 'consola'\n\nimport { readPid, removePidFile, writePid } from '~/daemon/pid'\n\nconst MAX_BACKOFF_MS = 60_000\nconst STABLE_THRESHOLD_MS = 60_000\n\nexport async function runAsSupervisor(runFn: () => Promise<void>): Promise<void> {\n let backoffMs = 1000\n let lastStartTime = Date.now()\n\n // Capture a fixed start time once. All subsequent writePid calls\n // reuse this value so it stays close to the OS process start time,\n // preventing isDaemonRunning() from rejecting us after crash-restarts\n // or PID file self-healing.\n const supervisorStartTime = Date.now()\n\n // Write PID file so status/stop/restart can find us.\n // This covers both the start -d path (where parent already wrote it)\n // and the enable path (where _supervisor is launched directly by the OS).\n writePid(process.pid, supervisorStartTime)\n\n const cleanup = () => {\n removePidFile()\n process.exit(0)\n }\n\n process.on('SIGTERM', cleanup)\n process.on('SIGINT', cleanup)\n\n // On Windows, SIGTERM doesn't fire - use 'exit' as fallback to clean up PID file\n if (process.platform === 'win32') {\n process.on('exit', () => {\n removePidFile()\n })\n }\n\n while (true) {\n // Self-heal: restore PID file if it was deleted externally.\n // Uses the fixed supervisorStartTime so the timestamp stays consistent\n // with the OS process start time.\n const existing = readPid()\n if (existing === null || existing.pid !== process.pid) {\n writePid(process.pid, supervisorStartTime)\n }\n lastStartTime = Date.now()\n try {\n await runFn()\n // runFn resolved normally — shouldn't happen for a long-running server,\n // but if it does, break the loop\n break\n }\n catch (error) {\n const uptime = Date.now() - lastStartTime\n consola.error('Server crashed:', error)\n\n if (uptime > STABLE_THRESHOLD_MS) {\n backoffMs = 1000\n }\n\n consola.info(`Restarting in ${backoffMs / 1000}s...`)\n await new Promise(resolve => setTimeout(resolve, backoffMs))\n backoffMs = Math.min(backoffMs * 2, MAX_BACKOFF_MS)\n }\n }\n}\n"],"mappings":";;;;;;AAKA,MAAM,iBAAiB;AACvB,MAAM,sBAAsB;AAE5B,eAAsB,gBAAgB,OAA2C;CAC/E,IAAI,YAAY;CAChB,IAAI,gBAAgB,KAAK,KAAK;CAM9B,MAAM,sBAAsB,KAAK,KAAK;AAKtC,UAAS,QAAQ,KAAK,oBAAoB;CAE1C,MAAM,gBAAgB;AACpB,iBAAe;AACf,UAAQ,KAAK,EAAE;;AAGjB,SAAQ,GAAG,WAAW,QAAQ;AAC9B,SAAQ,GAAG,UAAU,QAAQ;AAG7B,KAAI,QAAQ,aAAa,QACvB,SAAQ,GAAG,cAAc;AACvB,iBAAe;GACf;AAGJ,QAAO,MAAM;EAIX,MAAM,WAAW,SAAS;AAC1B,MAAI,aAAa,QAAQ,SAAS,QAAQ,QAAQ,IAChD,UAAS,QAAQ,KAAK,oBAAoB;AAE5C,kBAAgB,KAAK,KAAK;AAC1B,MAAI;AACF,SAAM,OAAO;AAGb;WAEK,OAAO;GACZ,MAAM,SAAS,KAAK,KAAK,GAAG;AAC5B,WAAQ,MAAM,mBAAmB,MAAM;AAEvC,OAAI,SAAS,oBACX,aAAY;AAGd,WAAQ,KAAK,iBAAiB,YAAY,IAAK,MAAM;AACrD,SAAM,IAAI,SAAQ,YAAW,WAAW,SAAS,UAAU,CAAC;AAC5D,eAAY,KAAK,IAAI,YAAY,GAAG,eAAe"}
@@ -34,13 +34,46 @@ function winQuoteArg(arg) {
34
34
  result += "\"";
35
35
  return result;
36
36
  }
37
- async function installAutoStart(execPath, args) {
37
+ /**
38
+ * Detect whether conhost --headless is available (Win10 1809+).
39
+ * Falls back to direct execution on older Windows versions.
40
+ */
41
+ function supportsHeadlessConhost() {
42
+ try {
43
+ execFileSync("conhost.exe", [
44
+ "--headless",
45
+ "cmd.exe",
46
+ "/c",
47
+ "exit",
48
+ "0"
49
+ ], {
50
+ stdio: "pipe",
51
+ timeout: 5e3
52
+ });
53
+ return true;
54
+ } catch {
55
+ return false;
56
+ }
57
+ }
58
+ function buildTaskXml(execPath, args, options = {}) {
38
59
  const xmlArgs = args.map((a) => winQuoteArg(a)).join(" ");
39
- const taskXml = `<?xml version="1.0" encoding="UTF-16"?>
60
+ const headless = options.useHeadlessConhost ?? supportsHeadlessConhost();
61
+ let command;
62
+ let commandArgs;
63
+ if (headless) {
64
+ command = "conhost.exe";
65
+ commandArgs = `--headless ${winQuoteArg(execPath)} ${xmlArgs}`;
66
+ } else {
67
+ if (options.useHeadlessConhost === void 0) consola.warn("conhost --headless not supported, console window may briefly appear on startup");
68
+ command = execPath;
69
+ commandArgs = xmlArgs;
70
+ }
71
+ return `<?xml version="1.0" encoding="UTF-16"?>
40
72
  <Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
41
73
  <Triggers>
42
74
  <LogonTrigger>
43
75
  <Enabled>true</Enabled>
76
+ <Delay>PT30S</Delay>
44
77
  </LogonTrigger>
45
78
  </Triggers>
46
79
  <Principals>
@@ -49,13 +82,38 @@ async function installAutoStart(execPath, args) {
49
82
  <RunLevel>LeastPrivilege</RunLevel>
50
83
  </Principal>
51
84
  </Principals>
85
+ <Settings>
86
+ <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
87
+ <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
88
+ <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
89
+ <AllowHardTerminate>true</AllowHardTerminate>
90
+ <StartWhenAvailable>true</StartWhenAvailable>
91
+ <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
92
+ <IdleSettings>
93
+ <StopOnIdleEnd>false</StopOnIdleEnd>
94
+ <RestartOnIdle>false</RestartOnIdle>
95
+ </IdleSettings>
96
+ <AllowStartOnDemand>true</AllowStartOnDemand>
97
+ <Enabled>true</Enabled>
98
+ <Hidden>true</Hidden>
99
+ <RunOnlyIfIdle>false</RunOnlyIfIdle>
100
+ <ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
101
+ <Priority>7</Priority>
102
+ <RestartOnFailure>
103
+ <Interval>PT1M</Interval>
104
+ <Count>3</Count>
105
+ </RestartOnFailure>
106
+ </Settings>
52
107
  <Actions>
53
108
  <Exec>
54
- <Command>${escapeXmlAttr(execPath)}</Command>
55
- <Arguments>${escapeXmlAttr(xmlArgs)}</Arguments>
109
+ <Command>${escapeXmlAttr(command)}</Command>
110
+ <Arguments>${escapeXmlAttr(commandArgs)}</Arguments>
56
111
  </Exec>
57
112
  </Actions>
58
113
  </Task>`;
114
+ }
115
+ async function installAutoStart(execPath, args) {
116
+ const taskXml = buildTaskXml(execPath, args);
59
117
  const tmpDir = os.tmpdir();
60
118
  const xmlPath = path.join(tmpDir, "copilot-proxy-task.xml");
61
119
  try {
@@ -96,4 +154,4 @@ async function uninstallAutoStart() {
96
154
 
97
155
  //#endregion
98
156
  export { installAutoStart, uninstallAutoStart };
99
- //# sourceMappingURL=win32-D1-MlKl7.js.map
157
+ //# sourceMappingURL=win32-DAStPxcF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"win32-DAStPxcF.js","names":["command: string","commandArgs: string"],"sources":["../src/daemon/platform/win32.ts"],"sourcesContent":["import { execFileSync } from 'node:child_process'\nimport fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport consola from 'consola'\n\nconst TASK_NAME = 'CopilotProxy'\n\nfunction escapeXmlAttr(s: string): string {\n return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;').replace(/'/g, '&apos;')\n}\n\n/**\n * Quote a single argument for Windows CommandLineToArgvW parsing.\n * See: https://learn.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments\n */\nfunction winQuoteArg(arg: string): string {\n if (arg.length > 0 && !/[\\s\"\\\\]/.test(arg))\n return arg\n\n let result = '\"'\n for (let i = 0; i < arg.length; i++) {\n let numBackslashes = 0\n while (i < arg.length && arg[i] === '\\\\') {\n numBackslashes++\n i++\n }\n if (i >= arg.length) {\n // End of string: double all backslashes\n result += '\\\\'.repeat(numBackslashes * 2)\n }\n else if (arg[i] === '\"') {\n // Before a quote: double backslashes, then escape the quote\n result += '\\\\'.repeat(numBackslashes * 2 + 1)\n result += '\"'\n }\n else {\n // Not before a quote: keep backslashes as-is\n result += '\\\\'.repeat(numBackslashes)\n result += arg[i]\n }\n }\n result += '\"'\n return result\n}\n\n/**\n * Detect whether conhost --headless is available (Win10 1809+).\n * Falls back to direct execution on older Windows versions.\n */\nfunction supportsHeadlessConhost(): boolean {\n try {\n execFileSync('conhost.exe', ['--headless', 'cmd.exe', '/c', 'exit', '0'], {\n stdio: 'pipe',\n timeout: 5000,\n })\n return true\n }\n catch {\n return false\n }\n}\n\ninterface BuildTaskXmlOptions {\n // Explicitly set for deterministic tests; undefined keeps runtime detection.\n useHeadlessConhost?: boolean\n}\n\nexport function buildTaskXml(execPath: string, args: string[], options: BuildTaskXmlOptions = {}): string {\n // Each arg is individually quoted for CommandLineToArgvW parsing,\n // then the whole string is XML-escaped for the task XML.\n const xmlArgs = args.map(a => winQuoteArg(a)).join(' ')\n\n // Use conhost --headless to hide console window when available\n const headless = options.useHeadlessConhost ?? supportsHeadlessConhost()\n let command: string\n let commandArgs: string\n if (headless) {\n command = 'conhost.exe'\n commandArgs = `--headless ${winQuoteArg(execPath)} ${xmlArgs}`\n }\n else {\n if (options.useHeadlessConhost === undefined) {\n consola.warn('conhost --headless not supported, console window may briefly appear on startup')\n }\n command = execPath\n commandArgs = xmlArgs\n }\n\n return `<?xml version=\"1.0\" encoding=\"UTF-16\"?>\n<Task version=\"1.2\" xmlns=\"http://schemas.microsoft.com/windows/2004/02/mit/task\">\n <Triggers>\n <LogonTrigger>\n <Enabled>true</Enabled>\n <Delay>PT30S</Delay>\n </LogonTrigger>\n </Triggers>\n <Principals>\n <Principal>\n <LogonType>InteractiveToken</LogonType>\n <RunLevel>LeastPrivilege</RunLevel>\n </Principal>\n </Principals>\n <Settings>\n <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>\n <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>\n <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>\n <AllowHardTerminate>true</AllowHardTerminate>\n <StartWhenAvailable>true</StartWhenAvailable>\n <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>\n <IdleSettings>\n <StopOnIdleEnd>false</StopOnIdleEnd>\n <RestartOnIdle>false</RestartOnIdle>\n </IdleSettings>\n <AllowStartOnDemand>true</AllowStartOnDemand>\n <Enabled>true</Enabled>\n <Hidden>true</Hidden>\n <RunOnlyIfIdle>false</RunOnlyIfIdle>\n <ExecutionTimeLimit>PT0S</ExecutionTimeLimit>\n <Priority>7</Priority>\n <RestartOnFailure>\n <Interval>PT1M</Interval>\n <Count>3</Count>\n </RestartOnFailure>\n </Settings>\n <Actions>\n <Exec>\n <Command>${escapeXmlAttr(command)}</Command>\n <Arguments>${escapeXmlAttr(commandArgs)}</Arguments>\n </Exec>\n </Actions>\n</Task>`\n}\n\nexport async function installAutoStart(execPath: string, args: string[]): Promise<boolean> {\n const taskXml = buildTaskXml(execPath, args)\n\n const tmpDir = os.tmpdir()\n const xmlPath = path.join(tmpDir, 'copilot-proxy-task.xml')\n\n try {\n fs.writeFileSync(xmlPath, taskXml, { encoding: 'utf16le' })\n\n execFileSync('schtasks', [\n '/create',\n '/tn',\n TASK_NAME,\n '/xml',\n xmlPath,\n '/f',\n ], { stdio: 'pipe' })\n }\n catch (error) {\n consola.error('Failed to create scheduled task:', error instanceof Error ? error.message : error)\n return false\n }\n finally {\n try {\n fs.unlinkSync(xmlPath)\n }\n catch {}\n }\n\n consola.success('Auto-start enabled via Task Scheduler')\n return true\n}\n\nexport async function uninstallAutoStart(): Promise<boolean> {\n try {\n execFileSync('schtasks', ['/delete', '/tn', TASK_NAME, '/f'], { stdio: 'pipe' })\n }\n catch (error) {\n consola.warn('Failed to delete scheduled task:', error instanceof Error ? error.message : error)\n }\n\n consola.success('Auto-start disabled')\n return true\n}\n"],"mappings":";;;;;;;AAMA,MAAM,YAAY;AAElB,SAAS,cAAc,GAAmB;AACxC,QAAO,EAAE,QAAQ,MAAM,QAAQ,CAAC,QAAQ,MAAM,OAAO,CAAC,QAAQ,MAAM,OAAO,CAAC,QAAQ,MAAM,SAAS,CAAC,QAAQ,MAAM,SAAS;;;;;;AAO7H,SAAS,YAAY,KAAqB;AACxC,KAAI,IAAI,SAAS,KAAK,CAAC,UAAU,KAAK,IAAI,CACxC,QAAO;CAET,IAAI,SAAS;AACb,MAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;EACnC,IAAI,iBAAiB;AACrB,SAAO,IAAI,IAAI,UAAU,IAAI,OAAO,MAAM;AACxC;AACA;;AAEF,MAAI,KAAK,IAAI,OAEX,WAAU,KAAK,OAAO,iBAAiB,EAAE;WAElC,IAAI,OAAO,MAAK;AAEvB,aAAU,KAAK,OAAO,iBAAiB,IAAI,EAAE;AAC7C,aAAU;SAEP;AAEH,aAAU,KAAK,OAAO,eAAe;AACrC,aAAU,IAAI;;;AAGlB,WAAU;AACV,QAAO;;;;;;AAOT,SAAS,0BAAmC;AAC1C,KAAI;AACF,eAAa,eAAe;GAAC;GAAc;GAAW;GAAM;GAAQ;GAAI,EAAE;GACxE,OAAO;GACP,SAAS;GACV,CAAC;AACF,SAAO;SAEH;AACJ,SAAO;;;AASX,SAAgB,aAAa,UAAkB,MAAgB,UAA+B,EAAE,EAAU;CAGxG,MAAM,UAAU,KAAK,KAAI,MAAK,YAAY,EAAE,CAAC,CAAC,KAAK,IAAI;CAGvD,MAAM,WAAW,QAAQ,sBAAsB,yBAAyB;CACxE,IAAIA;CACJ,IAAIC;AACJ,KAAI,UAAU;AACZ,YAAU;AACV,gBAAc,cAAc,YAAY,SAAS,CAAC,GAAG;QAElD;AACH,MAAI,QAAQ,uBAAuB,OACjC,SAAQ,KAAK,iFAAiF;AAEhG,YAAU;AACV,gBAAc;;AAGhB,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAsCQ,cAAc,QAAQ,CAAC;mBACrB,cAAc,YAAY,CAAC;;;;;AAM9C,eAAsB,iBAAiB,UAAkB,MAAkC;CACzF,MAAM,UAAU,aAAa,UAAU,KAAK;CAE5C,MAAM,SAAS,GAAG,QAAQ;CAC1B,MAAM,UAAU,KAAK,KAAK,QAAQ,yBAAyB;AAE3D,KAAI;AACF,KAAG,cAAc,SAAS,SAAS,EAAE,UAAU,WAAW,CAAC;AAE3D,eAAa,YAAY;GACvB;GACA;GACA;GACA;GACA;GACA;GACD,EAAE,EAAE,OAAO,QAAQ,CAAC;UAEhB,OAAO;AACZ,UAAQ,MAAM,oCAAoC,iBAAiB,QAAQ,MAAM,UAAU,MAAM;AACjG,SAAO;WAED;AACN,MAAI;AACF,MAAG,WAAW,QAAQ;UAElB;;AAGR,SAAQ,QAAQ,wCAAwC;AACxD,QAAO;;AAGT,eAAsB,qBAAuC;AAC3D,KAAI;AACF,eAAa,YAAY;GAAC;GAAW;GAAO;GAAW;GAAK,EAAE,EAAE,OAAO,QAAQ,CAAC;UAE3E,OAAO;AACZ,UAAQ,KAAK,oCAAoC,iBAAiB,QAAQ,MAAM,UAAU,MAAM;;AAGlG,SAAQ,QAAQ,sBAAsB;AACtC,QAAO"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jer-y/copilot-proxy",
3
3
  "type": "module",
4
- "version": "0.2.0",
4
+ "version": "0.2.2",
5
5
  "description": "Turn GitHub Copilot into an OpenAI/Anthropic-compatible server with Claude Code and Codex support.",
6
6
  "author": "jer-y",
7
7
  "homepage": "https://github.com/jer-y/copilot-proxy",
@@ -1 +0,0 @@
1
- {"version":3,"file":"pid-uKNpN4v-.js","names":["pid","error: unknown","cmdline: string"],"sources":["../src/daemon/pid.ts"],"sourcesContent":["import { execSync } from 'node:child_process'\nimport fs from 'node:fs'\nimport process from 'node:process'\n\nimport { PATHS } from '~/lib/paths'\n\nexport interface DaemonPidInfo {\n pid: number\n startTime: number\n}\n\nexport function writePid(pid: number): void {\n fs.mkdirSync(PATHS.APP_DIR, { recursive: true })\n const pidPath = PATHS.DAEMON_PID\n fs.writeFileSync(pidPath, `${pid}\\n${Date.now()}`, { mode: 0o600 })\n // Ensure permissions are correct even if file already existed with wider perms\n try {\n fs.chmodSync(pidPath, 0o600)\n }\n catch {}\n}\n\nexport function readPid(): DaemonPidInfo | null {\n try {\n const content = fs.readFileSync(PATHS.DAEMON_PID, 'utf8').trim()\n const lines = content.split('\\n')\n if (lines.length < 2) {\n // Legacy format: just PID\n const pid = Number.parseInt(lines[0], 10)\n if (Number.isNaN(pid) || pid <= 0 || String(pid) !== lines[0])\n return null\n return { pid, startTime: 0 }\n }\n const pid = Number.parseInt(lines[0], 10)\n const startTime = Number.parseInt(lines[1], 10)\n if (Number.isNaN(pid) || pid <= 0 || String(pid) !== lines[0])\n return null\n if (Number.isNaN(startTime))\n return null\n return { pid, startTime }\n }\n catch {\n return null\n }\n}\n\nexport function removePidFile(): void {\n try {\n fs.unlinkSync(PATHS.DAEMON_PID)\n }\n catch {}\n}\n\nexport function isProcessRunning(pid: number): boolean {\n try {\n process.kill(pid, 0)\n return true\n }\n catch (error: unknown) {\n if (error instanceof Error && 'code' in error && error.code === 'EPERM') {\n return true\n }\n return false\n }\n}\n\n/**\n * Check if a running process is actually our daemon supervisor.\n * Uses two checks:\n * 1. Command line must contain '_supervisor' flag\n * 2. Process start time must match the recorded startTime in PID file\n * (within tolerance, to prevent spoofing with a fake process)\n */\nfunction isOurDaemonProcess(pid: number, recordedStartTime: number): boolean {\n try {\n // Check command line\n let cmdline: string\n if (process.platform === 'win32') {\n cmdline = execSync(\n `wmic process where ProcessId=${pid} get CommandLine /format:list`,\n { stdio: 'pipe', encoding: 'utf8' },\n )\n }\n else {\n try {\n cmdline = fs.readFileSync(`/proc/${pid}/cmdline`, 'utf8')\n }\n catch {\n cmdline = execSync(`ps -p ${pid} -o command=`, {\n stdio: 'pipe',\n encoding: 'utf8',\n })\n }\n }\n\n if (!cmdline.includes('_supervisor'))\n return false\n\n // If no recorded start time (legacy), command line check alone is sufficient\n if (recordedStartTime === 0)\n return true\n\n // Check process start time matches what we recorded\n const processStartMs = getProcessStartTime(pid)\n if (processStartMs === null)\n return false\n\n // Allow 5s tolerance between when we recorded the time and when the OS recorded process start\n return Math.abs(processStartMs - recordedStartTime) < 5000\n }\n catch {\n return false\n }\n}\n\n/**\n * Get the start time of a process in milliseconds since epoch.\n * Returns null if unavailable.\n */\nfunction getProcessStartTime(pid: number): number | null {\n try {\n if (process.platform === 'win32') {\n const output = execSync(\n `wmic process where ProcessId=${pid} get CreationDate /format:list`,\n { stdio: 'pipe', encoding: 'utf8' },\n )\n // Format: CreationDate=20260302123456.123456+480 (offset is minutes from UTC)\n const match = output.match(/CreationDate=(\\d{4})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})\\.\\d+([+-]\\d+)/)\n if (!match)\n return null\n const [, y, m, d, h, min, s, offsetStr] = match\n const offsetMinutes = Number.parseInt(offsetStr, 10)\n // Parse as local time then adjust by the UTC offset\n const localMs = new Date(`${y}-${m}-${d}T${h}:${min}:${s}Z`).getTime()\n return localMs - offsetMinutes * 60 * 1000\n }\n else {\n // Linux: read /proc/<pid>/stat, field 22 is starttime in clock ticks since boot\n try {\n const stat = fs.readFileSync(`/proc/${pid}/stat`, 'utf8')\n // Fields are space-separated, but field 2 (comm) can contain spaces/parens\n // Find the last ')' to skip past comm field\n const afterComm = stat.substring(stat.lastIndexOf(')') + 2)\n const fields = afterComm.split(' ')\n // starttime is field 22 overall, which is field 20 after comm (0-indexed: index 19)\n const startTicks = Number.parseInt(fields[19], 10)\n if (Number.isNaN(startTicks))\n return null\n\n // Get system boot time and clock ticks per second\n const uptimeStr = fs.readFileSync('/proc/uptime', 'utf8')\n const uptimeSec = Number.parseFloat(uptimeStr.split(' ')[0])\n // CLK_TCK is typically 100 on Linux\n const clkTck = 100\n const processUptimeSec = startTicks / clkTck\n const bootTimeMs = Date.now() - uptimeSec * 1000\n return bootTimeMs + processUptimeSec * 1000\n }\n catch {\n // macOS: use ps -p <pid> -o lstart=\n const output = execSync(`ps -p ${pid} -o lstart=`, {\n stdio: 'pipe',\n encoding: 'utf8',\n }).trim()\n if (!output)\n return null\n const parsed = Date.parse(output)\n return Number.isNaN(parsed) ? null : parsed\n }\n }\n }\n catch {\n return null\n }\n}\n\n/**\n * Check if the PID from the PID file is likely still our daemon process.\n * Verifies that the process is alive AND that its command line contains\n * the _supervisor flag AND that its start time matches what we recorded.\n */\nexport function isDaemonRunning(): { running: true, pid: number } | { running: false } {\n const info = readPid()\n if (info === null)\n return { running: false }\n if (!isProcessRunning(info.pid))\n return { running: false }\n\n // Verify the process is actually our daemon\n if (!isOurDaemonProcess(info.pid, info.startTime))\n return { running: false }\n\n return { running: true, pid: info.pid }\n}\n"],"mappings":"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"}
@@ -1,6 +0,0 @@
1
- import "./paths-CA6OZ0WA.js";
2
- import "./config-D1kMGXKU.js";
3
- import "./pid-uKNpN4v-.js";
4
- import { daemonStart } from "./start-Dcv2sypx.js";
5
-
6
- export { daemonStart };
@@ -1 +0,0 @@
1
- {"version":3,"file":"supervisor-BbH28zwT.js","names":[],"sources":["../src/daemon/supervisor.ts"],"sourcesContent":["import process from 'node:process'\nimport consola from 'consola'\n\nimport { removePidFile } from '~/daemon/pid'\n\nconst MAX_BACKOFF_MS = 60_000\nconst STABLE_THRESHOLD_MS = 60_000\n\nexport async function runAsSupervisor(runFn: () => Promise<void>): Promise<void> {\n let backoffMs = 1000\n let lastStartTime = Date.now()\n\n const cleanup = () => {\n removePidFile()\n process.exit(0)\n }\n\n process.on('SIGTERM', cleanup)\n process.on('SIGINT', cleanup)\n\n // On Windows, SIGTERM doesn't fire - use 'exit' as fallback to clean up PID file\n if (process.platform === 'win32') {\n process.on('exit', () => {\n removePidFile()\n })\n }\n\n while (true) {\n lastStartTime = Date.now()\n try {\n await runFn()\n // runFn resolved normally — shouldn't happen for a long-running server,\n // but if it does, break the loop\n break\n }\n catch (error) {\n const uptime = Date.now() - lastStartTime\n consola.error('Server crashed:', error)\n\n if (uptime > STABLE_THRESHOLD_MS) {\n backoffMs = 1000\n }\n\n consola.info(`Restarting in ${backoffMs / 1000}s...`)\n await new Promise(resolve => setTimeout(resolve, backoffMs))\n backoffMs = Math.min(backoffMs * 2, MAX_BACKOFF_MS)\n }\n }\n}\n"],"mappings":";;;;;;AAKA,MAAM,iBAAiB;AACvB,MAAM,sBAAsB;AAE5B,eAAsB,gBAAgB,OAA2C;CAC/E,IAAI,YAAY;CAChB,IAAI,gBAAgB,KAAK,KAAK;CAE9B,MAAM,gBAAgB;AACpB,iBAAe;AACf,UAAQ,KAAK,EAAE;;AAGjB,SAAQ,GAAG,WAAW,QAAQ;AAC9B,SAAQ,GAAG,UAAU,QAAQ;AAG7B,KAAI,QAAQ,aAAa,QACvB,SAAQ,GAAG,cAAc;AACvB,iBAAe;GACf;AAGJ,QAAO,MAAM;AACX,kBAAgB,KAAK,KAAK;AAC1B,MAAI;AACF,SAAM,OAAO;AAGb;WAEK,OAAO;GACZ,MAAM,SAAS,KAAK,KAAK,GAAG;AAC5B,WAAQ,MAAM,mBAAmB,MAAM;AAEvC,OAAI,SAAS,oBACX,aAAY;AAGd,WAAQ,KAAK,iBAAiB,YAAY,IAAK,MAAM;AACrD,SAAM,IAAI,SAAQ,YAAW,WAAW,SAAS,UAAU,CAAC;AAC5D,eAAY,KAAK,IAAI,YAAY,GAAG,eAAe"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"win32-D1-MlKl7.js","names":[],"sources":["../src/daemon/platform/win32.ts"],"sourcesContent":["import { execFileSync } from 'node:child_process'\nimport fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport consola from 'consola'\n\nconst TASK_NAME = 'CopilotProxy'\n\nfunction escapeXmlAttr(s: string): string {\n return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;').replace(/'/g, '&apos;')\n}\n\n/**\n * Quote a single argument for Windows CommandLineToArgvW parsing.\n * See: https://learn.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments\n */\nfunction winQuoteArg(arg: string): string {\n if (arg.length > 0 && !/[\\s\"\\\\]/.test(arg))\n return arg\n\n let result = '\"'\n for (let i = 0; i < arg.length; i++) {\n let numBackslashes = 0\n while (i < arg.length && arg[i] === '\\\\') {\n numBackslashes++\n i++\n }\n if (i >= arg.length) {\n // End of string: double all backslashes\n result += '\\\\'.repeat(numBackslashes * 2)\n }\n else if (arg[i] === '\"') {\n // Before a quote: double backslashes, then escape the quote\n result += '\\\\'.repeat(numBackslashes * 2 + 1)\n result += '\"'\n }\n else {\n // Not before a quote: keep backslashes as-is\n result += '\\\\'.repeat(numBackslashes)\n result += arg[i]\n }\n }\n result += '\"'\n return result\n}\n\nexport async function installAutoStart(execPath: string, args: string[]): Promise<boolean> {\n // Use XML task definition for reliable argument handling\n // Each arg is individually quoted for CommandLineToArgvW parsing,\n // then the whole string is XML-escaped for the task XML.\n const xmlArgs = args.map(a => winQuoteArg(a)).join(' ')\n const taskXml = `<?xml version=\"1.0\" encoding=\"UTF-16\"?>\n<Task version=\"1.2\" xmlns=\"http://schemas.microsoft.com/windows/2004/02/mit/task\">\n <Triggers>\n <LogonTrigger>\n <Enabled>true</Enabled>\n </LogonTrigger>\n </Triggers>\n <Principals>\n <Principal>\n <LogonType>InteractiveToken</LogonType>\n <RunLevel>LeastPrivilege</RunLevel>\n </Principal>\n </Principals>\n <Actions>\n <Exec>\n <Command>${escapeXmlAttr(execPath)}</Command>\n <Arguments>${escapeXmlAttr(xmlArgs)}</Arguments>\n </Exec>\n </Actions>\n</Task>`\n\n const tmpDir = os.tmpdir()\n const xmlPath = path.join(tmpDir, 'copilot-proxy-task.xml')\n\n try {\n fs.writeFileSync(xmlPath, taskXml, { encoding: 'utf16le' })\n\n execFileSync('schtasks', [\n '/create',\n '/tn',\n TASK_NAME,\n '/xml',\n xmlPath,\n '/f',\n ], { stdio: 'pipe' })\n }\n catch (error) {\n consola.error('Failed to create scheduled task:', error instanceof Error ? error.message : error)\n return false\n }\n finally {\n try {\n fs.unlinkSync(xmlPath)\n }\n catch {}\n }\n\n consola.success('Auto-start enabled via Task Scheduler')\n return true\n}\n\nexport async function uninstallAutoStart(): Promise<boolean> {\n try {\n execFileSync('schtasks', ['/delete', '/tn', TASK_NAME, '/f'], { stdio: 'pipe' })\n }\n catch (error) {\n consola.warn('Failed to delete scheduled task:', error instanceof Error ? error.message : error)\n }\n\n consola.success('Auto-start disabled')\n return true\n}\n"],"mappings":";;;;;;;AAMA,MAAM,YAAY;AAElB,SAAS,cAAc,GAAmB;AACxC,QAAO,EAAE,QAAQ,MAAM,QAAQ,CAAC,QAAQ,MAAM,OAAO,CAAC,QAAQ,MAAM,OAAO,CAAC,QAAQ,MAAM,SAAS,CAAC,QAAQ,MAAM,SAAS;;;;;;AAO7H,SAAS,YAAY,KAAqB;AACxC,KAAI,IAAI,SAAS,KAAK,CAAC,UAAU,KAAK,IAAI,CACxC,QAAO;CAET,IAAI,SAAS;AACb,MAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;EACnC,IAAI,iBAAiB;AACrB,SAAO,IAAI,IAAI,UAAU,IAAI,OAAO,MAAM;AACxC;AACA;;AAEF,MAAI,KAAK,IAAI,OAEX,WAAU,KAAK,OAAO,iBAAiB,EAAE;WAElC,IAAI,OAAO,MAAK;AAEvB,aAAU,KAAK,OAAO,iBAAiB,IAAI,EAAE;AAC7C,aAAU;SAEP;AAEH,aAAU,KAAK,OAAO,eAAe;AACrC,aAAU,IAAI;;;AAGlB,WAAU;AACV,QAAO;;AAGT,eAAsB,iBAAiB,UAAkB,MAAkC;CAIzF,MAAM,UAAU,KAAK,KAAI,MAAK,YAAY,EAAE,CAAC,CAAC,KAAK,IAAI;CACvD,MAAM,UAAU;;;;;;;;;;;;;;;iBAeD,cAAc,SAAS,CAAC;mBACtB,cAAc,QAAQ,CAAC;;;;CAKxC,MAAM,SAAS,GAAG,QAAQ;CAC1B,MAAM,UAAU,KAAK,KAAK,QAAQ,yBAAyB;AAE3D,KAAI;AACF,KAAG,cAAc,SAAS,SAAS,EAAE,UAAU,WAAW,CAAC;AAE3D,eAAa,YAAY;GACvB;GACA;GACA;GACA;GACA;GACA;GACD,EAAE,EAAE,OAAO,QAAQ,CAAC;UAEhB,OAAO;AACZ,UAAQ,MAAM,oCAAoC,iBAAiB,QAAQ,MAAM,UAAU,MAAM;AACjG,SAAO;WAED;AACN,MAAI;AACF,MAAG,WAAW,QAAQ;UAElB;;AAGR,SAAQ,QAAQ,wCAAwC;AACxD,QAAO;;AAGT,eAAsB,qBAAuC;AAC3D,KAAI;AACF,eAAa,YAAY;GAAC;GAAW;GAAO;GAAW;GAAK,EAAE,EAAE,OAAO,QAAQ,CAAC;UAE3E,OAAO;AACZ,UAAQ,KAAK,oCAAoC,iBAAiB,QAAQ,MAAM,UAAU,MAAM;;AAGlG,SAAQ,QAAQ,sBAAsB;AACtC,QAAO"}