@jer-y/copilot-proxy 0.1.6 → 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/README.md +44 -1
- package/README.zh-CN.md +44 -1
- package/dist/config-D1kMGXKU.js +38 -0
- package/dist/config-D1kMGXKU.js.map +1 -0
- package/dist/config-ixm2Pm96.js +4 -0
- package/dist/darwin-BVmd1DeO.js +69 -0
- package/dist/darwin-BVmd1DeO.js.map +1 -0
- package/dist/linux-CX0xETja.js +103 -0
- package/dist/linux-CX0xETja.js.map +1 -0
- package/dist/main.js +321 -37
- package/dist/main.js.map +1 -1
- package/dist/paths-CA6OZ0WA.js +33 -0
- package/dist/paths-CA6OZ0WA.js.map +1 -0
- package/dist/pid-DtjYMiQS.js +138 -0
- package/dist/pid-DtjYMiQS.js.map +1 -0
- package/dist/start-BNocR0hU.js +107 -0
- package/dist/start-BNocR0hU.js.map +1 -0
- package/dist/start-CUT1hJrN.js +6 -0
- package/dist/supervisor-wpaa2IAJ.js +43 -0
- package/dist/supervisor-wpaa2IAJ.js.map +1 -0
- package/dist/win32-D1-MlKl7.js +99 -0
- package/dist/win32-D1-MlKl7.js.map +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
//#region src/lib/paths.ts
|
|
6
|
+
const APP_DIR = path.join(os.homedir(), ".local", "share", "copilot-proxy");
|
|
7
|
+
const GITHUB_TOKEN_PATH = path.join(APP_DIR, "github_token");
|
|
8
|
+
const DAEMON_PID = path.join(APP_DIR, "daemon.pid");
|
|
9
|
+
const DAEMON_LOG = path.join(APP_DIR, "daemon.log");
|
|
10
|
+
const DAEMON_JSON = path.join(APP_DIR, "daemon.json");
|
|
11
|
+
const PATHS = {
|
|
12
|
+
APP_DIR,
|
|
13
|
+
GITHUB_TOKEN_PATH,
|
|
14
|
+
DAEMON_PID,
|
|
15
|
+
DAEMON_LOG,
|
|
16
|
+
DAEMON_JSON
|
|
17
|
+
};
|
|
18
|
+
async function ensurePaths() {
|
|
19
|
+
await fs.mkdir(PATHS.APP_DIR, { recursive: true });
|
|
20
|
+
await ensureFile(PATHS.GITHUB_TOKEN_PATH);
|
|
21
|
+
}
|
|
22
|
+
async function ensureFile(filePath) {
|
|
23
|
+
try {
|
|
24
|
+
await fs.access(filePath, fs.constants.W_OK);
|
|
25
|
+
} catch {
|
|
26
|
+
await fs.writeFile(filePath, "");
|
|
27
|
+
await fs.chmod(filePath, 384);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
//#endregion
|
|
32
|
+
export { PATHS, ensurePaths };
|
|
33
|
+
//# sourceMappingURL=paths-CA6OZ0WA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths-CA6OZ0WA.js","names":[],"sources":["../src/lib/paths.ts"],"sourcesContent":["import fs from 'node:fs/promises'\nimport os from 'node:os'\nimport path from 'node:path'\n\nconst APP_DIR = path.join(os.homedir(), '.local', 'share', 'copilot-proxy')\n\nconst GITHUB_TOKEN_PATH = path.join(APP_DIR, 'github_token')\nconst DAEMON_PID = path.join(APP_DIR, 'daemon.pid')\nconst DAEMON_LOG = path.join(APP_DIR, 'daemon.log')\nconst DAEMON_JSON = path.join(APP_DIR, 'daemon.json')\n\nexport const PATHS = {\n APP_DIR,\n GITHUB_TOKEN_PATH,\n DAEMON_PID,\n DAEMON_LOG,\n DAEMON_JSON,\n}\n\nexport async function ensurePaths(): Promise<void> {\n await fs.mkdir(PATHS.APP_DIR, { recursive: true })\n await ensureFile(PATHS.GITHUB_TOKEN_PATH)\n}\n\nasync function ensureFile(filePath: string): Promise<void> {\n try {\n await fs.access(filePath, fs.constants.W_OK)\n }\n catch {\n await fs.writeFile(filePath, '')\n await fs.chmod(filePath, 0o600)\n }\n}\n"],"mappings":";;;;;AAIA,MAAM,UAAU,KAAK,KAAK,GAAG,SAAS,EAAE,UAAU,SAAS,gBAAgB;AAE3E,MAAM,oBAAoB,KAAK,KAAK,SAAS,eAAe;AAC5D,MAAM,aAAa,KAAK,KAAK,SAAS,aAAa;AACnD,MAAM,aAAa,KAAK,KAAK,SAAS,aAAa;AACnD,MAAM,cAAc,KAAK,KAAK,SAAS,cAAc;AAErD,MAAa,QAAQ;CACnB;CACA;CACA;CACA;CACA;CACD;AAED,eAAsB,cAA6B;AACjD,OAAM,GAAG,MAAM,MAAM,SAAS,EAAE,WAAW,MAAM,CAAC;AAClD,OAAM,WAAW,MAAM,kBAAkB;;AAG3C,eAAe,WAAW,UAAiC;AACzD,KAAI;AACF,QAAM,GAAG,OAAO,UAAU,GAAG,UAAU,KAAK;SAExC;AACJ,QAAM,GAAG,UAAU,UAAU,GAAG;AAChC,QAAM,GAAG,MAAM,UAAU,IAAM"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { PATHS } from "./paths-CA6OZ0WA.js";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import { execSync } from "node:child_process";
|
|
5
|
+
|
|
6
|
+
//#region src/daemon/pid.ts
|
|
7
|
+
function writePid(pid, startTime) {
|
|
8
|
+
fs.mkdirSync(PATHS.APP_DIR, { recursive: true });
|
|
9
|
+
const pidPath = PATHS.DAEMON_PID;
|
|
10
|
+
fs.writeFileSync(pidPath, `${pid}\n${startTime ?? Date.now()}`, { mode: 384 });
|
|
11
|
+
try {
|
|
12
|
+
fs.chmodSync(pidPath, 384);
|
|
13
|
+
} catch {}
|
|
14
|
+
}
|
|
15
|
+
function readPid() {
|
|
16
|
+
try {
|
|
17
|
+
const lines = fs.readFileSync(PATHS.DAEMON_PID, "utf8").trim().split("\n");
|
|
18
|
+
if (lines.length < 2) {
|
|
19
|
+
const pid$1 = Number.parseInt(lines[0], 10);
|
|
20
|
+
if (Number.isNaN(pid$1) || pid$1 <= 0 || String(pid$1) !== lines[0]) return null;
|
|
21
|
+
return {
|
|
22
|
+
pid: pid$1,
|
|
23
|
+
startTime: 0
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const pid = Number.parseInt(lines[0], 10);
|
|
27
|
+
const startTime = Number.parseInt(lines[1], 10);
|
|
28
|
+
if (Number.isNaN(pid) || pid <= 0 || String(pid) !== lines[0]) return null;
|
|
29
|
+
if (Number.isNaN(startTime)) return null;
|
|
30
|
+
return {
|
|
31
|
+
pid,
|
|
32
|
+
startTime
|
|
33
|
+
};
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function removePidFile() {
|
|
39
|
+
try {
|
|
40
|
+
fs.unlinkSync(PATHS.DAEMON_PID);
|
|
41
|
+
} catch {}
|
|
42
|
+
}
|
|
43
|
+
function isProcessRunning(pid) {
|
|
44
|
+
try {
|
|
45
|
+
process.kill(pid, 0);
|
|
46
|
+
return true;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (error instanceof Error && "code" in error && error.code === "EPERM") return true;
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Check if a running process is actually our daemon supervisor.
|
|
54
|
+
* Uses two checks:
|
|
55
|
+
* 1. Command line must contain '_supervisor' flag
|
|
56
|
+
* 2. Process start time must match the recorded startTime in PID file
|
|
57
|
+
* (within tolerance, to prevent spoofing with a fake process)
|
|
58
|
+
*/
|
|
59
|
+
function isOurDaemonProcess(pid, recordedStartTime) {
|
|
60
|
+
try {
|
|
61
|
+
let cmdline;
|
|
62
|
+
if (process.platform === "win32") cmdline = execSync(`wmic process where ProcessId=${pid} get CommandLine /format:list`, {
|
|
63
|
+
stdio: "pipe",
|
|
64
|
+
encoding: "utf8"
|
|
65
|
+
});
|
|
66
|
+
else try {
|
|
67
|
+
cmdline = fs.readFileSync(`/proc/${pid}/cmdline`, "utf8");
|
|
68
|
+
} catch {
|
|
69
|
+
cmdline = execSync(`ps -p ${pid} -o command=`, {
|
|
70
|
+
stdio: "pipe",
|
|
71
|
+
encoding: "utf8"
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
if (!cmdline.includes("_supervisor")) return false;
|
|
75
|
+
if (recordedStartTime === 0) return true;
|
|
76
|
+
const processStartMs = getProcessStartTime(pid);
|
|
77
|
+
if (processStartMs === null) return false;
|
|
78
|
+
return Math.abs(processStartMs - recordedStartTime) < 5e3;
|
|
79
|
+
} catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get the start time of a process in milliseconds since epoch.
|
|
85
|
+
* Returns null if unavailable.
|
|
86
|
+
*/
|
|
87
|
+
function getProcessStartTime(pid) {
|
|
88
|
+
try {
|
|
89
|
+
if (process.platform === "win32") {
|
|
90
|
+
const match = execSync(`wmic process where ProcessId=${pid} get CreationDate /format:list`, {
|
|
91
|
+
stdio: "pipe",
|
|
92
|
+
encoding: "utf8"
|
|
93
|
+
}).match(/CreationDate=(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\.\d+([+-]\d+)/);
|
|
94
|
+
if (!match) return null;
|
|
95
|
+
const [, y, m, d, h, min, s, offsetStr] = match;
|
|
96
|
+
const offsetMinutes = Number.parseInt(offsetStr, 10);
|
|
97
|
+
return (/* @__PURE__ */ new Date(`${y}-${m}-${d}T${h}:${min}:${s}Z`)).getTime() - offsetMinutes * 60 * 1e3;
|
|
98
|
+
} else try {
|
|
99
|
+
const stat = fs.readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
100
|
+
const fields = stat.substring(stat.lastIndexOf(")") + 2).split(" ");
|
|
101
|
+
const startTicks = Number.parseInt(fields[19], 10);
|
|
102
|
+
if (Number.isNaN(startTicks)) return null;
|
|
103
|
+
const uptimeStr = fs.readFileSync("/proc/uptime", "utf8");
|
|
104
|
+
const uptimeSec = Number.parseFloat(uptimeStr.split(" ")[0]);
|
|
105
|
+
const processUptimeSec = startTicks / 100;
|
|
106
|
+
return Date.now() - uptimeSec * 1e3 + processUptimeSec * 1e3;
|
|
107
|
+
} catch {
|
|
108
|
+
const output = execSync(`ps -p ${pid} -o lstart=`, {
|
|
109
|
+
stdio: "pipe",
|
|
110
|
+
encoding: "utf8"
|
|
111
|
+
}).trim();
|
|
112
|
+
if (!output) return null;
|
|
113
|
+
const parsed = Date.parse(output);
|
|
114
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Check if the PID from the PID file is likely still our daemon process.
|
|
122
|
+
* Verifies that the process is alive AND that its command line contains
|
|
123
|
+
* the _supervisor flag AND that its start time matches what we recorded.
|
|
124
|
+
*/
|
|
125
|
+
function isDaemonRunning() {
|
|
126
|
+
const info = readPid();
|
|
127
|
+
if (info === null) return { running: false };
|
|
128
|
+
if (!isProcessRunning(info.pid)) return { running: false };
|
|
129
|
+
if (!isOurDaemonProcess(info.pid, info.startTime)) return { running: false };
|
|
130
|
+
return {
|
|
131
|
+
running: true,
|
|
132
|
+
pid: info.pid
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
//#endregion
|
|
137
|
+
export { isDaemonRunning, isProcessRunning, readPid, removePidFile, writePid };
|
|
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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"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { PATHS } from "./paths-CA6OZ0WA.js";
|
|
2
|
+
import { saveDaemonConfig } from "./config-D1kMGXKU.js";
|
|
3
|
+
import { isDaemonRunning, removePidFile, writePid } from "./pid-DtjYMiQS.js";
|
|
4
|
+
import consola from "consola";
|
|
5
|
+
import process from "node:process";
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
|
+
|
|
9
|
+
//#region src/daemon/start.ts
|
|
10
|
+
const LOCK_PATH = `${PATHS.DAEMON_PID}.lock`;
|
|
11
|
+
function acquireLock() {
|
|
12
|
+
try {
|
|
13
|
+
fs.mkdirSync(PATHS.APP_DIR, { recursive: true });
|
|
14
|
+
const fd = fs.openSync(LOCK_PATH, "wx");
|
|
15
|
+
fs.writeSync(fd, String(process.pid));
|
|
16
|
+
fs.closeSync(fd);
|
|
17
|
+
return true;
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function releaseLock() {
|
|
23
|
+
try {
|
|
24
|
+
fs.unlinkSync(LOCK_PATH);
|
|
25
|
+
} catch {}
|
|
26
|
+
}
|
|
27
|
+
function ensureLock() {
|
|
28
|
+
if (acquireLock()) return;
|
|
29
|
+
try {
|
|
30
|
+
const lockPid = Number.parseInt(fs.readFileSync(LOCK_PATH, "utf8").trim(), 10);
|
|
31
|
+
if (!Number.isNaN(lockPid) && lockPid > 0) try {
|
|
32
|
+
process.kill(lockPid, 0);
|
|
33
|
+
consola.error("Another start operation is in progress");
|
|
34
|
+
process.exit(1);
|
|
35
|
+
} catch {
|
|
36
|
+
releaseLock();
|
|
37
|
+
if (!acquireLock()) {
|
|
38
|
+
consola.error("Failed to acquire start lock");
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
releaseLock();
|
|
44
|
+
if (!acquireLock()) {
|
|
45
|
+
consola.error("Failed to acquire start lock");
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
consola.error("Failed to acquire start lock");
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function daemonStart(config) {
|
|
55
|
+
ensureLock();
|
|
56
|
+
const exitWithLock = (code) => {
|
|
57
|
+
releaseLock();
|
|
58
|
+
process.exit(code);
|
|
59
|
+
throw new Error("unreachable");
|
|
60
|
+
};
|
|
61
|
+
const daemon = isDaemonRunning();
|
|
62
|
+
if (daemon.running) {
|
|
63
|
+
consola.error(`Daemon is already running (PID: ${daemon.pid})`);
|
|
64
|
+
exitWithLock(1);
|
|
65
|
+
}
|
|
66
|
+
saveDaemonConfig(config);
|
|
67
|
+
if (config.githubToken) {
|
|
68
|
+
fs.mkdirSync(PATHS.APP_DIR, { recursive: true });
|
|
69
|
+
fs.writeFileSync(PATHS.GITHUB_TOKEN_PATH, config.githubToken, { mode: 384 });
|
|
70
|
+
try {
|
|
71
|
+
fs.chmodSync(PATHS.GITHUB_TOKEN_PATH, 384);
|
|
72
|
+
} catch {}
|
|
73
|
+
}
|
|
74
|
+
const execPath = process.argv[0];
|
|
75
|
+
const scriptPath = process.argv[1];
|
|
76
|
+
const logStream = fs.openSync(PATHS.DAEMON_LOG, "a", 384);
|
|
77
|
+
try {
|
|
78
|
+
fs.fchmodSync(logStream, 384);
|
|
79
|
+
} catch {}
|
|
80
|
+
const child = spawn(execPath, [
|
|
81
|
+
scriptPath,
|
|
82
|
+
"start",
|
|
83
|
+
"--_supervisor"
|
|
84
|
+
], {
|
|
85
|
+
detached: true,
|
|
86
|
+
stdio: [
|
|
87
|
+
"ignore",
|
|
88
|
+
logStream,
|
|
89
|
+
logStream
|
|
90
|
+
],
|
|
91
|
+
env: process.env
|
|
92
|
+
});
|
|
93
|
+
if (child.pid === void 0) {
|
|
94
|
+
consola.error("Failed to start daemon process");
|
|
95
|
+
removePidFile();
|
|
96
|
+
return exitWithLock(1);
|
|
97
|
+
}
|
|
98
|
+
writePid(child.pid);
|
|
99
|
+
child.unref();
|
|
100
|
+
consola.success(`Daemon started (PID: ${child.pid})`);
|
|
101
|
+
consola.info(`Logs: ${PATHS.DAEMON_LOG}`);
|
|
102
|
+
exitWithLock(0);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
//#endregion
|
|
106
|
+
export { daemonStart };
|
|
107
|
+
//# sourceMappingURL=start-BNocR0hU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import "./paths-CA6OZ0WA.js";
|
|
2
|
+
import { readPid, removePidFile, writePid } from "./pid-DtjYMiQS.js";
|
|
3
|
+
import consola from "consola";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
|
|
6
|
+
//#region src/daemon/supervisor.ts
|
|
7
|
+
const MAX_BACKOFF_MS = 6e4;
|
|
8
|
+
const STABLE_THRESHOLD_MS = 6e4;
|
|
9
|
+
async function runAsSupervisor(runFn) {
|
|
10
|
+
let backoffMs = 1e3;
|
|
11
|
+
let lastStartTime = Date.now();
|
|
12
|
+
const supervisorStartTime = Date.now();
|
|
13
|
+
writePid(process.pid, supervisorStartTime);
|
|
14
|
+
const cleanup = () => {
|
|
15
|
+
removePidFile();
|
|
16
|
+
process.exit(0);
|
|
17
|
+
};
|
|
18
|
+
process.on("SIGTERM", cleanup);
|
|
19
|
+
process.on("SIGINT", cleanup);
|
|
20
|
+
if (process.platform === "win32") process.on("exit", () => {
|
|
21
|
+
removePidFile();
|
|
22
|
+
});
|
|
23
|
+
while (true) {
|
|
24
|
+
const existing = readPid();
|
|
25
|
+
if (existing === null || existing.pid !== process.pid) writePid(process.pid, supervisorStartTime);
|
|
26
|
+
lastStartTime = Date.now();
|
|
27
|
+
try {
|
|
28
|
+
await runFn();
|
|
29
|
+
break;
|
|
30
|
+
} catch (error) {
|
|
31
|
+
const uptime = Date.now() - lastStartTime;
|
|
32
|
+
consola.error("Server crashed:", error);
|
|
33
|
+
if (uptime > STABLE_THRESHOLD_MS) backoffMs = 1e3;
|
|
34
|
+
consola.info(`Restarting in ${backoffMs / 1e3}s...`);
|
|
35
|
+
await new Promise((resolve) => setTimeout(resolve, backoffMs));
|
|
36
|
+
backoffMs = Math.min(backoffMs * 2, MAX_BACKOFF_MS);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
//#endregion
|
|
42
|
+
export { runAsSupervisor };
|
|
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"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import consola from "consola";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import { execFileSync } from "node:child_process";
|
|
6
|
+
|
|
7
|
+
//#region src/daemon/platform/win32.ts
|
|
8
|
+
const TASK_NAME = "CopilotProxy";
|
|
9
|
+
function escapeXmlAttr(s) {
|
|
10
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Quote a single argument for Windows CommandLineToArgvW parsing.
|
|
14
|
+
* See: https://learn.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments
|
|
15
|
+
*/
|
|
16
|
+
function winQuoteArg(arg) {
|
|
17
|
+
if (arg.length > 0 && !/[\s"\\]/.test(arg)) return arg;
|
|
18
|
+
let result = "\"";
|
|
19
|
+
for (let i = 0; i < arg.length; i++) {
|
|
20
|
+
let numBackslashes = 0;
|
|
21
|
+
while (i < arg.length && arg[i] === "\\") {
|
|
22
|
+
numBackslashes++;
|
|
23
|
+
i++;
|
|
24
|
+
}
|
|
25
|
+
if (i >= arg.length) result += "\\".repeat(numBackslashes * 2);
|
|
26
|
+
else if (arg[i] === "\"") {
|
|
27
|
+
result += "\\".repeat(numBackslashes * 2 + 1);
|
|
28
|
+
result += "\"";
|
|
29
|
+
} else {
|
|
30
|
+
result += "\\".repeat(numBackslashes);
|
|
31
|
+
result += arg[i];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
result += "\"";
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
async function installAutoStart(execPath, args) {
|
|
38
|
+
const xmlArgs = args.map((a) => winQuoteArg(a)).join(" ");
|
|
39
|
+
const taskXml = `<?xml version="1.0" encoding="UTF-16"?>
|
|
40
|
+
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
41
|
+
<Triggers>
|
|
42
|
+
<LogonTrigger>
|
|
43
|
+
<Enabled>true</Enabled>
|
|
44
|
+
</LogonTrigger>
|
|
45
|
+
</Triggers>
|
|
46
|
+
<Principals>
|
|
47
|
+
<Principal>
|
|
48
|
+
<LogonType>InteractiveToken</LogonType>
|
|
49
|
+
<RunLevel>LeastPrivilege</RunLevel>
|
|
50
|
+
</Principal>
|
|
51
|
+
</Principals>
|
|
52
|
+
<Actions>
|
|
53
|
+
<Exec>
|
|
54
|
+
<Command>${escapeXmlAttr(execPath)}</Command>
|
|
55
|
+
<Arguments>${escapeXmlAttr(xmlArgs)}</Arguments>
|
|
56
|
+
</Exec>
|
|
57
|
+
</Actions>
|
|
58
|
+
</Task>`;
|
|
59
|
+
const tmpDir = os.tmpdir();
|
|
60
|
+
const xmlPath = path.join(tmpDir, "copilot-proxy-task.xml");
|
|
61
|
+
try {
|
|
62
|
+
fs.writeFileSync(xmlPath, taskXml, { encoding: "utf16le" });
|
|
63
|
+
execFileSync("schtasks", [
|
|
64
|
+
"/create",
|
|
65
|
+
"/tn",
|
|
66
|
+
TASK_NAME,
|
|
67
|
+
"/xml",
|
|
68
|
+
xmlPath,
|
|
69
|
+
"/f"
|
|
70
|
+
], { stdio: "pipe" });
|
|
71
|
+
} catch (error) {
|
|
72
|
+
consola.error("Failed to create scheduled task:", error instanceof Error ? error.message : error);
|
|
73
|
+
return false;
|
|
74
|
+
} finally {
|
|
75
|
+
try {
|
|
76
|
+
fs.unlinkSync(xmlPath);
|
|
77
|
+
} catch {}
|
|
78
|
+
}
|
|
79
|
+
consola.success("Auto-start enabled via Task Scheduler");
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
async function uninstallAutoStart() {
|
|
83
|
+
try {
|
|
84
|
+
execFileSync("schtasks", [
|
|
85
|
+
"/delete",
|
|
86
|
+
"/tn",
|
|
87
|
+
TASK_NAME,
|
|
88
|
+
"/f"
|
|
89
|
+
], { stdio: "pipe" });
|
|
90
|
+
} catch (error) {
|
|
91
|
+
consola.warn("Failed to delete scheduled task:", error instanceof Error ? error.message : error);
|
|
92
|
+
}
|
|
93
|
+
consola.success("Auto-start disabled");
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
//#endregion
|
|
98
|
+
export { installAutoStart, uninstallAutoStart };
|
|
99
|
+
//# sourceMappingURL=win32-D1-MlKl7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"').replace(/'/g, ''')\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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jer-y/copilot-proxy",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1
|
|
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",
|
|
@@ -23,14 +23,14 @@
|
|
|
23
23
|
],
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "tsdown",
|
|
26
|
-
"dev": "bun run --watch ./src/main.ts",
|
|
26
|
+
"dev": "bun run --watch ./src/main.ts start",
|
|
27
27
|
"knip": "knip-bun",
|
|
28
28
|
"lint": "eslint --cache",
|
|
29
29
|
"lint:all": "eslint --cache .",
|
|
30
30
|
"prepack": "npm run build",
|
|
31
31
|
"prepare": "test -d .git && npx simple-git-hooks || true",
|
|
32
32
|
"release": "bumpp && npm publish --access public",
|
|
33
|
-
"start": "NODE_ENV=production bun run ./src/main.ts",
|
|
33
|
+
"start": "NODE_ENV=production bun run ./src/main.ts start",
|
|
34
34
|
"typecheck": "tsc"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|