@jer-y/copilot-proxy 0.2.0 → 0.2.1
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 +4 -4
- package/dist/{pid-uKNpN4v-.js → pid-DtjYMiQS.js} +3 -3
- package/dist/pid-DtjYMiQS.js.map +1 -0
- package/dist/{start-Dcv2sypx.js → start-BNocR0hU.js} +2 -2
- package/dist/{start-Dcv2sypx.js.map → start-BNocR0hU.js.map} +1 -1
- package/dist/start-CUT1hJrN.js +6 -0
- package/dist/{supervisor-BbH28zwT.js → supervisor-wpaa2IAJ.js} +6 -2
- package/dist/supervisor-wpaa2IAJ.js.map +1 -0
- package/package.json +1 -1
- package/dist/pid-uKNpN4v-.js.map +0 -1
- package/dist/start-C-0OcnXB.js +0 -6
- package/dist/supervisor-BbH28zwT.js.map +0 -1
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-
|
|
5
|
-
import { daemonStart } from "./start-
|
|
4
|
+
import { isDaemonRunning, isProcessRunning, readPid, removePidFile } from "./pid-DtjYMiQS.js";
|
|
5
|
+
import { daemonStart } from "./start-BNocR0hU.js";
|
|
6
6
|
import { defineCommand, runMain } from "citty";
|
|
7
7
|
import consola from "consola";
|
|
8
8
|
import fs from "node:fs/promises";
|
|
@@ -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-
|
|
1977
|
+
const { runAsSupervisor } = await import("./supervisor-wpaa2IAJ.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-
|
|
1997
|
+
const { daemonStart: daemonStart$1 } = await import("./start-CUT1hJrN.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 {}
|
|
@@ -135,4 +135,4 @@ function isDaemonRunning() {
|
|
|
135
135
|
|
|
136
136
|
//#endregion
|
|
137
137
|
export { isDaemonRunning, isProcessRunning, readPid, removePidFile, writePid };
|
|
138
|
-
//# sourceMappingURL=pid-
|
|
138
|
+
//# sourceMappingURL=pid-DtjYMiQS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pid-DtjYMiQS.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 }).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 +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-
|
|
3
|
+
import { isDaemonRunning, removePidFile, writePid } from "./pid-DtjYMiQS.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-
|
|
107
|
+
//# sourceMappingURL=start-BNocR0hU.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start-
|
|
1
|
+
{"version":3,"file":"start-BNocR0hU.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,5 +1,5 @@
|
|
|
1
1
|
import "./paths-CA6OZ0WA.js";
|
|
2
|
-
import { removePidFile } from "./pid-
|
|
2
|
+
import { readPid, removePidFile, writePid } from "./pid-DtjYMiQS.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-
|
|
43
|
+
//# sourceMappingURL=supervisor-wpaa2IAJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supervisor-wpaa2IAJ.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"}
|
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.
|
|
4
|
+
"version": "0.2.1",
|
|
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",
|
package/dist/pid-uKNpN4v-.js.map
DELETED
|
@@ -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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"}
|
package/dist/start-C-0OcnXB.js
DELETED
|
@@ -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"}
|