@jer-y/copilot-proxy 0.1.5 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +367 -43
- 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-uKNpN4v-.js +138 -0
- package/dist/pid-uKNpN4v-.js.map +1 -0
- package/dist/start-C-0OcnXB.js +6 -0
- package/dist/start-Dcv2sypx.js +107 -0
- package/dist/start-Dcv2sypx.js.map +1 -0
- package/dist/supervisor-BbH28zwT.js +39 -0
- package/dist/supervisor-BbH28zwT.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
package/README.md
CHANGED
|
@@ -42,6 +42,8 @@ A reverse-engineered proxy for the GitHub Copilot API that exposes it as an Open
|
|
|
42
42
|
- **Token Visibility**: Option to display GitHub and Copilot tokens during authentication and refresh for debugging (`--show-token`).
|
|
43
43
|
- **Flexible Authentication**: Authenticate interactively or provide a GitHub token directly, suitable for CI/CD environments.
|
|
44
44
|
- **Support for Different Account Types**: Works with individual, business, and enterprise GitHub Copilot plans.
|
|
45
|
+
- **Background Daemon Mode**: Run the proxy as a background service with `start -d`, with automatic crash recovery and exponential backoff restart. Manage with `stop`, `restart`, `status`, and `logs` commands.
|
|
46
|
+
- **Cross-Platform Auto-Start**: Register the proxy as an auto-start service on Linux (systemd), macOS (launchd), and Windows (Task Scheduler) with `enable`/`disable` commands.
|
|
45
47
|
|
|
46
48
|
## Prerequisites
|
|
47
49
|
|
|
@@ -186,7 +188,13 @@ npx @jer-y/copilot-proxy@latest auth
|
|
|
186
188
|
|
|
187
189
|
Copilot API now uses a subcommand structure with these main commands:
|
|
188
190
|
|
|
189
|
-
- `start`: Start the Copilot API server. This command will also handle authentication if needed.
|
|
191
|
+
- `start`: Start the Copilot API server. This command will also handle authentication if needed. Use `-d` to run as a background daemon.
|
|
192
|
+
- `stop`: Stop the background daemon.
|
|
193
|
+
- `restart`: Restart the background daemon using saved configuration.
|
|
194
|
+
- `status`: Show daemon status (PID, port, start time).
|
|
195
|
+
- `logs`: View daemon logs. Use `-f` to follow in real time.
|
|
196
|
+
- `enable`: Register the proxy as an auto-start service (systemd/launchd/Task Scheduler).
|
|
197
|
+
- `disable`: Remove the auto-start service registration.
|
|
190
198
|
- `auth`: Run GitHub authentication flow without starting the server. This is typically used if you need to generate a token for use with the `--github-token` option, especially in non-interactive environments.
|
|
191
199
|
- `check-usage`: Show your current GitHub Copilot usage and quota information directly in the terminal (no server required).
|
|
192
200
|
- `debug`: Display diagnostic information including version, runtime details, file paths, and authentication status. Useful for troubleshooting and support.
|
|
@@ -209,6 +217,7 @@ The following command line options are available for the `start` command:
|
|
|
209
217
|
| --claude-code | Generate a command to launch Claude Code with Copilot API config | false | -c |
|
|
210
218
|
| --show-token | Show GitHub and Copilot tokens on fetch and refresh | false | none |
|
|
211
219
|
| --proxy-env | Initialize proxy from environment variables | false | none |
|
|
220
|
+
| --daemon | Run as a background daemon with crash recovery | false | -d |
|
|
212
221
|
|
|
213
222
|
### Auth Command Options
|
|
214
223
|
|
|
@@ -223,6 +232,13 @@ The following command line options are available for the `start` command:
|
|
|
223
232
|
| ------ | ------------------------- | ------- | ----- |
|
|
224
233
|
| --json | Output debug info as JSON | false | none |
|
|
225
234
|
|
|
235
|
+
### Logs Command Options
|
|
236
|
+
|
|
237
|
+
| Option | Description | Default | Alias |
|
|
238
|
+
| ------- | --------------------- | ------- | ----- |
|
|
239
|
+
| --follow | Follow log output | false | -f |
|
|
240
|
+
| --lines | Number of lines to show | 50 | -n |
|
|
241
|
+
|
|
226
242
|
## API Endpoints
|
|
227
243
|
|
|
228
244
|
The server exposes several endpoints to interact with the Copilot API. It provides OpenAI-compatible endpoints and Anthropic-compatible endpoints, allowing for greater flexibility with different tools and services. All endpoints are available with or without the `/v1/` prefix.
|
|
@@ -309,6 +325,33 @@ npx @jer-y/copilot-proxy@latest debug --json
|
|
|
309
325
|
|
|
310
326
|
# Initialize proxy from environment variables (HTTP_PROXY, HTTPS_PROXY, etc.)
|
|
311
327
|
npx @jer-y/copilot-proxy@latest start --proxy-env
|
|
328
|
+
|
|
329
|
+
# Start as a background daemon
|
|
330
|
+
npx @jer-y/copilot-proxy@latest start -d
|
|
331
|
+
|
|
332
|
+
# Start daemon on a custom port with a GitHub token
|
|
333
|
+
npx @jer-y/copilot-proxy@latest start -d --port 8080 --github-token ghp_YOUR_TOKEN
|
|
334
|
+
|
|
335
|
+
# Check daemon status
|
|
336
|
+
npx @jer-y/copilot-proxy@latest status
|
|
337
|
+
|
|
338
|
+
# View daemon logs (last 50 lines)
|
|
339
|
+
npx @jer-y/copilot-proxy@latest logs
|
|
340
|
+
|
|
341
|
+
# Follow daemon logs in real time
|
|
342
|
+
npx @jer-y/copilot-proxy@latest logs -f
|
|
343
|
+
|
|
344
|
+
# Restart the daemon
|
|
345
|
+
npx @jer-y/copilot-proxy@latest restart
|
|
346
|
+
|
|
347
|
+
# Stop the daemon
|
|
348
|
+
npx @jer-y/copilot-proxy@latest stop
|
|
349
|
+
|
|
350
|
+
# Register as auto-start service (systemd/launchd/Task Scheduler)
|
|
351
|
+
npx @jer-y/copilot-proxy@latest enable
|
|
352
|
+
|
|
353
|
+
# Remove auto-start registration
|
|
354
|
+
npx @jer-y/copilot-proxy@latest disable
|
|
312
355
|
```
|
|
313
356
|
|
|
314
357
|
## Using the Usage Viewer
|
package/README.zh-CN.md
CHANGED
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
- **Token 可视化**:`--show-token` 显示 GitHub/Copilot token 便于调试。
|
|
43
43
|
- **灵活认证**:支持交互式登录或直接传入 GitHub token,适用于 CI/CD。
|
|
44
44
|
- **多账号类型**:支持个人、企业、组织三种 Copilot 账户类型。
|
|
45
|
+
- **后台守护模式**:通过 `start -d` 将代理作为后台服务运行,支持崩溃自动恢复与指数退避重启。配合 `stop`、`restart`、`status`、`logs` 管理。
|
|
46
|
+
- **跨平台开机自启**:通过 `enable`/`disable` 注册为系统自启动服务,支持 Linux(systemd)、macOS(launchd)和 Windows(任务计划程序)。
|
|
45
47
|
|
|
46
48
|
## 前置要求
|
|
47
49
|
|
|
@@ -184,7 +186,13 @@ npx @jer-y/copilot-proxy@latest auth
|
|
|
184
186
|
|
|
185
187
|
Copilot API 使用子命令结构,主要命令如下:
|
|
186
188
|
|
|
187
|
-
- `start`:启动 Copilot API
|
|
189
|
+
- `start`:启动 Copilot API 服务(必要时会自动认证)。使用 `-d` 可作为后台守护进程运行。
|
|
190
|
+
- `stop`:停止后台守护进程。
|
|
191
|
+
- `restart`:使用已保存的配置重启后台守护进程。
|
|
192
|
+
- `status`:查看守护进程状态(PID、端口、启动时间)。
|
|
193
|
+
- `logs`:查看守护进程日志。使用 `-f` 实时跟踪。
|
|
194
|
+
- `enable`:注册为系统自启动服务(systemd / launchd / 任务计划程序)。
|
|
195
|
+
- `disable`:移除自启动服务注册。
|
|
188
196
|
- `auth`:仅进行 GitHub 认证,不启动服务,常用于生成 `--github-token`(CI/CD 场景)。
|
|
189
197
|
- `check-usage`:直接查看 Copilot 使用量/配额(无需启动服务)。
|
|
190
198
|
- `debug`:输出诊断信息,包括版本、运行环境、路径与认证状态。
|
|
@@ -205,6 +213,7 @@ Copilot API 使用子命令结构,主要命令如下:
|
|
|
205
213
|
| --claude-code | 生成 Claude Code 配置命令 | false | -c |
|
|
206
214
|
| --show-token | 在获取/刷新时显示 GitHub/Copilot token | false | 无 |
|
|
207
215
|
| --proxy-env | 从环境变量初始化代理(HTTP_PROXY/HTTPS_PROXY 等) | false | 无 |
|
|
216
|
+
| --daemon | 作为后台守护进程运行,支持崩溃自动恢复 | false | -d |
|
|
208
217
|
|
|
209
218
|
### auth 参数
|
|
210
219
|
|
|
@@ -219,6 +228,13 @@ Copilot API 使用子命令结构,主要命令如下:
|
|
|
219
228
|
| ------ | -------------------- | ------ | ---- |
|
|
220
229
|
| --json | 以 JSON 输出调试信息 | false | 无 |
|
|
221
230
|
|
|
231
|
+
### logs 参数
|
|
232
|
+
|
|
233
|
+
| 参数 | 说明 | 默认值 | 简写 |
|
|
234
|
+
| -------- | -------------- | ------ | ---- |
|
|
235
|
+
| --follow | 实时跟踪日志 | false | -f |
|
|
236
|
+
| --lines | 显示行数 | 50 | -n |
|
|
237
|
+
|
|
222
238
|
## API 端点
|
|
223
239
|
|
|
224
240
|
服务提供多组端点,以兼容 OpenAI / Anthropic API。所有端点均支持有无 `/v1/` 前缀。
|
|
@@ -301,6 +317,33 @@ npx @jer-y/copilot-proxy@latest debug --json
|
|
|
301
317
|
|
|
302
318
|
# 从环境变量初始化代理
|
|
303
319
|
npx @jer-y/copilot-proxy@latest start --proxy-env
|
|
320
|
+
|
|
321
|
+
# 后台守护进程模式启动
|
|
322
|
+
npx @jer-y/copilot-proxy@latest start -d
|
|
323
|
+
|
|
324
|
+
# 指定端口 + GitHub token 启动守护进程
|
|
325
|
+
npx @jer-y/copilot-proxy@latest start -d --port 8080 --github-token ghp_YOUR_TOKEN
|
|
326
|
+
|
|
327
|
+
# 查看守护进程状态
|
|
328
|
+
npx @jer-y/copilot-proxy@latest status
|
|
329
|
+
|
|
330
|
+
# 查看日志(最后 50 行)
|
|
331
|
+
npx @jer-y/copilot-proxy@latest logs
|
|
332
|
+
|
|
333
|
+
# 实时跟踪日志
|
|
334
|
+
npx @jer-y/copilot-proxy@latest logs -f
|
|
335
|
+
|
|
336
|
+
# 重启守护进程
|
|
337
|
+
npx @jer-y/copilot-proxy@latest restart
|
|
338
|
+
|
|
339
|
+
# 停止守护进程
|
|
340
|
+
npx @jer-y/copilot-proxy@latest stop
|
|
341
|
+
|
|
342
|
+
# 注册为开机自启服务(systemd / launchd / 任务计划程序)
|
|
343
|
+
npx @jer-y/copilot-proxy@latest enable
|
|
344
|
+
|
|
345
|
+
# 移除开机自启
|
|
346
|
+
npx @jer-y/copilot-proxy@latest disable
|
|
304
347
|
```
|
|
305
348
|
|
|
306
349
|
## 使用用量面板
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { PATHS } from "./paths-CA6OZ0WA.js";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
|
|
4
|
+
//#region src/daemon/config.ts
|
|
5
|
+
const VALID_ACCOUNT_TYPES = [
|
|
6
|
+
"individual",
|
|
7
|
+
"business",
|
|
8
|
+
"enterprise"
|
|
9
|
+
];
|
|
10
|
+
function saveDaemonConfig(config) {
|
|
11
|
+
const { githubToken: _removed,...safeConfig } = config;
|
|
12
|
+
fs.mkdirSync(PATHS.APP_DIR, { recursive: true });
|
|
13
|
+
fs.writeFileSync(PATHS.DAEMON_JSON, JSON.stringify(safeConfig, null, 2), { mode: 384 });
|
|
14
|
+
try {
|
|
15
|
+
fs.chmodSync(PATHS.DAEMON_JSON, 384);
|
|
16
|
+
} catch {}
|
|
17
|
+
}
|
|
18
|
+
function loadDaemonConfig() {
|
|
19
|
+
try {
|
|
20
|
+
const content = fs.readFileSync(PATHS.DAEMON_JSON, "utf8");
|
|
21
|
+
const data = JSON.parse(content);
|
|
22
|
+
if (typeof data.port !== "number" || !Number.isInteger(data.port) || data.port <= 0 || data.port > 65535) return null;
|
|
23
|
+
if (typeof data.verbose !== "boolean") return null;
|
|
24
|
+
if (typeof data.accountType !== "string" || !VALID_ACCOUNT_TYPES.includes(data.accountType)) return null;
|
|
25
|
+
if (typeof data.manual !== "boolean") return null;
|
|
26
|
+
if (typeof data.rateLimitWait !== "boolean") return null;
|
|
27
|
+
if (typeof data.showToken !== "boolean") return null;
|
|
28
|
+
if (typeof data.proxyEnv !== "boolean") return null;
|
|
29
|
+
if (data.rateLimit !== void 0 && (typeof data.rateLimit !== "number" || !Number.isInteger(data.rateLimit) || data.rateLimit <= 0 || data.rateLimit > 86400)) return null;
|
|
30
|
+
return data;
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
//#endregion
|
|
37
|
+
export { loadDaemonConfig, saveDaemonConfig };
|
|
38
|
+
//# sourceMappingURL=config-D1kMGXKU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-D1kMGXKU.js","names":[],"sources":["../src/daemon/config.ts"],"sourcesContent":["import fs from 'node:fs'\n\nimport { PATHS } from '~/lib/paths'\n\nexport interface DaemonConfig {\n port: number\n verbose: boolean\n accountType: string\n manual: boolean\n rateLimit?: number\n rateLimitWait: boolean\n githubToken?: string\n showToken: boolean\n proxyEnv: boolean\n}\n\nconst VALID_ACCOUNT_TYPES = ['individual', 'business', 'enterprise']\n\nexport function saveDaemonConfig(config: DaemonConfig): void {\n const { githubToken: _removed, ...safeConfig } = config\n fs.mkdirSync(PATHS.APP_DIR, { recursive: true })\n fs.writeFileSync(PATHS.DAEMON_JSON, JSON.stringify(safeConfig, null, 2), { mode: 0o600 })\n // Ensure permissions are correct even if file already existed with wider perms\n try {\n fs.chmodSync(PATHS.DAEMON_JSON, 0o600)\n }\n catch {}\n}\n\nexport function loadDaemonConfig(): DaemonConfig | null {\n try {\n const content = fs.readFileSync(PATHS.DAEMON_JSON, 'utf8')\n const data = JSON.parse(content) as Record<string, unknown>\n\n // Runtime validation of critical fields\n if (typeof data.port !== 'number' || !Number.isInteger(data.port) || data.port <= 0 || data.port > 65535)\n return null\n if (typeof data.verbose !== 'boolean')\n return null\n if (typeof data.accountType !== 'string' || !VALID_ACCOUNT_TYPES.includes(data.accountType))\n return null\n if (typeof data.manual !== 'boolean')\n return null\n if (typeof data.rateLimitWait !== 'boolean')\n return null\n if (typeof data.showToken !== 'boolean')\n return null\n if (typeof data.proxyEnv !== 'boolean')\n return null\n if (data.rateLimit !== undefined && (typeof data.rateLimit !== 'number' || !Number.isInteger(data.rateLimit) || data.rateLimit <= 0 || data.rateLimit > 86400))\n return null\n\n return data as unknown as DaemonConfig\n }\n catch {\n return null\n }\n}\n"],"mappings":";;;;AAgBA,MAAM,sBAAsB;CAAC;CAAc;CAAY;CAAa;AAEpE,SAAgB,iBAAiB,QAA4B;CAC3D,MAAM,EAAE,aAAa,SAAU,GAAG,eAAe;AACjD,IAAG,UAAU,MAAM,SAAS,EAAE,WAAW,MAAM,CAAC;AAChD,IAAG,cAAc,MAAM,aAAa,KAAK,UAAU,YAAY,MAAM,EAAE,EAAE,EAAE,MAAM,KAAO,CAAC;AAEzF,KAAI;AACF,KAAG,UAAU,MAAM,aAAa,IAAM;SAElC;;AAGR,SAAgB,mBAAwC;AACtD,KAAI;EACF,MAAM,UAAU,GAAG,aAAa,MAAM,aAAa,OAAO;EAC1D,MAAM,OAAO,KAAK,MAAM,QAAQ;AAGhC,MAAI,OAAO,KAAK,SAAS,YAAY,CAAC,OAAO,UAAU,KAAK,KAAK,IAAI,KAAK,QAAQ,KAAK,KAAK,OAAO,MACjG,QAAO;AACT,MAAI,OAAO,KAAK,YAAY,UAC1B,QAAO;AACT,MAAI,OAAO,KAAK,gBAAgB,YAAY,CAAC,oBAAoB,SAAS,KAAK,YAAY,CACzF,QAAO;AACT,MAAI,OAAO,KAAK,WAAW,UACzB,QAAO;AACT,MAAI,OAAO,KAAK,kBAAkB,UAChC,QAAO;AACT,MAAI,OAAO,KAAK,cAAc,UAC5B,QAAO;AACT,MAAI,OAAO,KAAK,aAAa,UAC3B,QAAO;AACT,MAAI,KAAK,cAAc,WAAc,OAAO,KAAK,cAAc,YAAY,CAAC,OAAO,UAAU,KAAK,UAAU,IAAI,KAAK,aAAa,KAAK,KAAK,YAAY,OACtJ,QAAO;AAET,SAAO;SAEH;AACJ,SAAO"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { PATHS } from "./paths-CA6OZ0WA.js";
|
|
2
|
+
import consola from "consola";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import { execFileSync } from "node:child_process";
|
|
7
|
+
|
|
8
|
+
//#region src/daemon/platform/darwin.ts
|
|
9
|
+
const PLIST_NAME = "com.copilot-proxy.plist";
|
|
10
|
+
const LAUNCH_AGENTS_DIR = path.join(os.homedir(), "Library", "LaunchAgents");
|
|
11
|
+
const PLIST_PATH = path.join(LAUNCH_AGENTS_DIR, PLIST_NAME);
|
|
12
|
+
function xmlEscape(s) {
|
|
13
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
14
|
+
}
|
|
15
|
+
async function installAutoStart(execPath, args) {
|
|
16
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
17
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
18
|
+
<plist version="1.0">
|
|
19
|
+
<dict>
|
|
20
|
+
<key>Label</key>
|
|
21
|
+
<string>com.copilot-proxy</string>
|
|
22
|
+
<key>ProgramArguments</key>
|
|
23
|
+
<array>
|
|
24
|
+
${[execPath, ...args].map((arg) => ` <string>${xmlEscape(arg)}</string>`).join("\n")}
|
|
25
|
+
</array>
|
|
26
|
+
<key>RunAtLoad</key>
|
|
27
|
+
<true/>
|
|
28
|
+
<key>KeepAlive</key>
|
|
29
|
+
<true/>
|
|
30
|
+
<key>StandardOutPath</key>
|
|
31
|
+
<string>${xmlEscape(PATHS.DAEMON_LOG)}</string>
|
|
32
|
+
<key>StandardErrorPath</key>
|
|
33
|
+
<string>${xmlEscape(PATHS.DAEMON_LOG)}</string>
|
|
34
|
+
</dict>
|
|
35
|
+
</plist>
|
|
36
|
+
`;
|
|
37
|
+
fs.mkdirSync(LAUNCH_AGENTS_DIR, { recursive: true });
|
|
38
|
+
fs.writeFileSync(PLIST_PATH, plist);
|
|
39
|
+
try {
|
|
40
|
+
execFileSync("launchctl", ["load", PLIST_PATH]);
|
|
41
|
+
} catch {
|
|
42
|
+
consola.error("launchctl load failed. You may need to load it manually.");
|
|
43
|
+
consola.info(`Plist written to: ${PLIST_PATH}`);
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
consola.success("Auto-start enabled via launchd");
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
async function uninstallAutoStart() {
|
|
50
|
+
try {
|
|
51
|
+
execFileSync("launchctl", ["unload", PLIST_PATH], { stdio: "pipe" });
|
|
52
|
+
} catch (error) {
|
|
53
|
+
consola.warn("Failed to unload service:", error instanceof Error ? error.message : error);
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
fs.unlinkSync(PLIST_PATH);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (error instanceof Error && "code" in error && error.code !== "ENOENT") {
|
|
59
|
+
consola.error("Failed to remove plist file:", error.message);
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
consola.success("Auto-start disabled");
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
//#endregion
|
|
68
|
+
export { installAutoStart, uninstallAutoStart };
|
|
69
|
+
//# sourceMappingURL=darwin-BVmd1DeO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"darwin-BVmd1DeO.js","names":["error: unknown"],"sources":["../src/daemon/platform/darwin.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\nimport { PATHS } from '~/lib/paths'\n\nconst PLIST_NAME = 'com.copilot-proxy.plist'\nconst LAUNCH_AGENTS_DIR = path.join(os.homedir(), 'Library', 'LaunchAgents')\nconst PLIST_PATH = path.join(LAUNCH_AGENTS_DIR, PLIST_NAME)\n\nfunction xmlEscape(s: string): string {\n return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"')\n}\n\nexport async function installAutoStart(execPath: string, args: string[]): Promise<boolean> {\n const programArgs = [execPath, ...args]\n .map(arg => ` <string>${xmlEscape(arg)}</string>`)\n .join('\\n')\n\n const plist = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>com.copilot-proxy</string>\n <key>ProgramArguments</key>\n <array>\n${programArgs}\n </array>\n <key>RunAtLoad</key>\n <true/>\n <key>KeepAlive</key>\n <true/>\n <key>StandardOutPath</key>\n <string>${xmlEscape(PATHS.DAEMON_LOG)}</string>\n <key>StandardErrorPath</key>\n <string>${xmlEscape(PATHS.DAEMON_LOG)}</string>\n</dict>\n</plist>\n`\n\n fs.mkdirSync(LAUNCH_AGENTS_DIR, { recursive: true })\n fs.writeFileSync(PLIST_PATH, plist)\n\n try {\n execFileSync('launchctl', ['load', PLIST_PATH])\n }\n catch {\n consola.error('launchctl load failed. You may need to load it manually.')\n consola.info(`Plist written to: ${PLIST_PATH}`)\n return false\n }\n\n consola.success('Auto-start enabled via launchd')\n return true\n}\n\nexport async function uninstallAutoStart(): Promise<boolean> {\n try {\n execFileSync('launchctl', ['unload', PLIST_PATH], { stdio: 'pipe' })\n }\n catch (error) {\n consola.warn('Failed to unload service:', error instanceof Error ? error.message : error)\n }\n\n try {\n fs.unlinkSync(PLIST_PATH)\n }\n catch (error: unknown) {\n if (error instanceof Error && 'code' in error && error.code !== 'ENOENT') {\n consola.error('Failed to remove plist file:', error.message)\n return false\n }\n }\n\n consola.success('Auto-start disabled')\n return true\n}\n"],"mappings":";;;;;;;;AAQA,MAAM,aAAa;AACnB,MAAM,oBAAoB,KAAK,KAAK,GAAG,SAAS,EAAE,WAAW,eAAe;AAC5E,MAAM,aAAa,KAAK,KAAK,mBAAmB,WAAW;AAE3D,SAAS,UAAU,GAAmB;AACpC,QAAO,EAAE,QAAQ,MAAM,QAAQ,CAAC,QAAQ,MAAM,OAAO,CAAC,QAAQ,MAAM,OAAO,CAAC,QAAQ,MAAM,SAAS;;AAGrG,eAAsB,iBAAiB,UAAkB,MAAkC;CAKzF,MAAM,QAAQ;;;;;;;;EAJM,CAAC,UAAU,GAAG,KAAK,CACpC,KAAI,QAAO,mBAAmB,UAAU,IAAI,CAAC,WAAW,CACxD,KAAK,KAAK,CAUD;;;;;;;cAOA,UAAU,MAAM,WAAW,CAAC;;cAE5B,UAAU,MAAM,WAAW,CAAC;;;;AAKxC,IAAG,UAAU,mBAAmB,EAAE,WAAW,MAAM,CAAC;AACpD,IAAG,cAAc,YAAY,MAAM;AAEnC,KAAI;AACF,eAAa,aAAa,CAAC,QAAQ,WAAW,CAAC;SAE3C;AACJ,UAAQ,MAAM,2DAA2D;AACzE,UAAQ,KAAK,qBAAqB,aAAa;AAC/C,SAAO;;AAGT,SAAQ,QAAQ,iCAAiC;AACjD,QAAO;;AAGT,eAAsB,qBAAuC;AAC3D,KAAI;AACF,eAAa,aAAa,CAAC,UAAU,WAAW,EAAE,EAAE,OAAO,QAAQ,CAAC;UAE/D,OAAO;AACZ,UAAQ,KAAK,6BAA6B,iBAAiB,QAAQ,MAAM,UAAU,MAAM;;AAG3F,KAAI;AACF,KAAG,WAAW,WAAW;UAEpBA,OAAgB;AACrB,MAAI,iBAAiB,SAAS,UAAU,SAAS,MAAM,SAAS,UAAU;AACxE,WAAQ,MAAM,gCAAgC,MAAM,QAAQ;AAC5D,UAAO;;;AAIX,SAAQ,QAAQ,sBAAsB;AACtC,QAAO"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { PATHS } from "./paths-CA6OZ0WA.js";
|
|
2
|
+
import consola from "consola";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import { execSync } from "node:child_process";
|
|
7
|
+
|
|
8
|
+
//#region src/daemon/platform/linux.ts
|
|
9
|
+
const SERVICE_NAME = "copilot-proxy";
|
|
10
|
+
const SERVICE_DIR = path.join(os.homedir(), ".config", "systemd", "user");
|
|
11
|
+
const SERVICE_PATH = path.join(SERVICE_DIR, `${SERVICE_NAME}.service`);
|
|
12
|
+
function shellQuote(s) {
|
|
13
|
+
const escaped = s.replace(/%/g, "%%");
|
|
14
|
+
if (/^[\w/.:-]+$/.test(escaped)) return escaped;
|
|
15
|
+
return `"${escaped.replace(/"/g, "\\\"")}"`;
|
|
16
|
+
}
|
|
17
|
+
async function installAutoStart(execPath, args) {
|
|
18
|
+
try {
|
|
19
|
+
execSync("which systemctl", { stdio: "pipe" });
|
|
20
|
+
} catch {
|
|
21
|
+
consola.error("systemctl not found. Cannot register systemd service.");
|
|
22
|
+
consola.info("You may need to manually configure auto-start for your init system.");
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const unit = `[Unit]
|
|
26
|
+
Description=Copilot API Proxy
|
|
27
|
+
After=network-online.target
|
|
28
|
+
|
|
29
|
+
[Service]
|
|
30
|
+
ExecStart=${shellQuote(execPath)} ${args.map((a) => shellQuote(a)).join(" ")}
|
|
31
|
+
Restart=on-failure
|
|
32
|
+
RestartSec=5
|
|
33
|
+
StandardOutput=append:${PATHS.DAEMON_LOG.replace(/%/g, "%%")}
|
|
34
|
+
StandardError=append:${PATHS.DAEMON_LOG.replace(/%/g, "%%")}
|
|
35
|
+
|
|
36
|
+
[Install]
|
|
37
|
+
WantedBy=default.target
|
|
38
|
+
`;
|
|
39
|
+
fs.mkdirSync(SERVICE_DIR, { recursive: true });
|
|
40
|
+
fs.writeFileSync(SERVICE_PATH, unit);
|
|
41
|
+
try {
|
|
42
|
+
execSync("systemctl --user daemon-reload", { stdio: "pipe" });
|
|
43
|
+
} catch {
|
|
44
|
+
consola.error("Failed to reload systemd. Is systemd running in user mode?");
|
|
45
|
+
consola.info("On WSL2, you may need to enable systemd: https://learn.microsoft.com/en-us/windows/wsl/systemd");
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
execSync(`systemctl --user enable --now ${SERVICE_NAME}`, { stdio: "pipe" });
|
|
50
|
+
} catch (error) {
|
|
51
|
+
consola.error("Failed to enable service:", error instanceof Error ? error.message : error);
|
|
52
|
+
consola.info(`Service file written to: ${SERVICE_PATH}`);
|
|
53
|
+
consola.info("You can try manually: systemctl --user enable --now copilot-proxy");
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
execSync(`loginctl enable-linger ${os.userInfo().username}`);
|
|
58
|
+
} catch {
|
|
59
|
+
consola.warn("Could not enable linger. Service may not run when logged out.");
|
|
60
|
+
}
|
|
61
|
+
consola.success("Auto-start enabled via systemd");
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
async function uninstallAutoStart() {
|
|
65
|
+
if (!fs.existsSync(SERVICE_PATH)) {
|
|
66
|
+
consola.info("Auto-start service is not installed");
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
let hadErrors = false;
|
|
70
|
+
try {
|
|
71
|
+
execSync(`systemctl --user stop ${SERVICE_NAME}`, { stdio: "pipe" });
|
|
72
|
+
} catch {}
|
|
73
|
+
try {
|
|
74
|
+
execSync(`systemctl --user disable ${SERVICE_NAME}`, { stdio: "pipe" });
|
|
75
|
+
} catch (error) {
|
|
76
|
+
consola.warn("Failed to disable service:", error instanceof Error ? error.message : error);
|
|
77
|
+
hadErrors = true;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
fs.unlinkSync(SERVICE_PATH);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (error instanceof Error && "code" in error && error.code !== "ENOENT") {
|
|
83
|
+
consola.error("Failed to remove service file:", error.message);
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
execSync("systemctl --user daemon-reload", { stdio: "pipe" });
|
|
89
|
+
} catch (error) {
|
|
90
|
+
consola.warn("Failed to reload systemd:", error instanceof Error ? error.message : error);
|
|
91
|
+
hadErrors = true;
|
|
92
|
+
}
|
|
93
|
+
if (hadErrors) {
|
|
94
|
+
consola.warn("Auto-start disabled with warnings");
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
consola.success("Auto-start disabled");
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
//#endregion
|
|
102
|
+
export { installAutoStart, uninstallAutoStart };
|
|
103
|
+
//# sourceMappingURL=linux-CX0xETja.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linux-CX0xETja.js","names":["error: unknown"],"sources":["../src/daemon/platform/linux.ts"],"sourcesContent":["import { execSync } from 'node:child_process'\nimport fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport consola from 'consola'\n\nimport { PATHS } from '~/lib/paths'\n\nconst SERVICE_NAME = 'copilot-proxy'\nconst SERVICE_DIR = path.join(os.homedir(), '.config', 'systemd', 'user')\nconst SERVICE_PATH = path.join(SERVICE_DIR, `${SERVICE_NAME}.service`)\n\nfunction shellQuote(s: string): string {\n // Escape % for systemd (% is a specifier prefix in unit files)\n const escaped = s.replace(/%/g, '%%')\n if (/^[\\w/.:-]+$/.test(escaped))\n return escaped\n return `\"${escaped.replace(/\"/g, '\\\\\"')}\"`\n}\n\nexport async function installAutoStart(execPath: string, args: string[]): Promise<boolean> {\n try {\n execSync('which systemctl', { stdio: 'pipe' })\n }\n catch {\n consola.error('systemctl not found. Cannot register systemd service.')\n consola.info('You may need to manually configure auto-start for your init system.')\n return false\n }\n\n const unit = `[Unit]\nDescription=Copilot API Proxy\nAfter=network-online.target\n\n[Service]\nExecStart=${shellQuote(execPath)} ${args.map(a => shellQuote(a)).join(' ')}\nRestart=on-failure\nRestartSec=5\nStandardOutput=append:${PATHS.DAEMON_LOG.replace(/%/g, '%%')}\nStandardError=append:${PATHS.DAEMON_LOG.replace(/%/g, '%%')}\n\n[Install]\nWantedBy=default.target\n`\n\n fs.mkdirSync(SERVICE_DIR, { recursive: true })\n fs.writeFileSync(SERVICE_PATH, unit)\n\n try {\n execSync('systemctl --user daemon-reload', { stdio: 'pipe' })\n }\n catch {\n consola.error('Failed to reload systemd. Is systemd running in user mode?')\n consola.info('On WSL2, you may need to enable systemd: https://learn.microsoft.com/en-us/windows/wsl/systemd')\n return false\n }\n\n try {\n execSync(`systemctl --user enable --now ${SERVICE_NAME}`, { stdio: 'pipe' })\n }\n catch (error) {\n consola.error('Failed to enable service:', error instanceof Error ? error.message : error)\n consola.info(`Service file written to: ${SERVICE_PATH}`)\n consola.info('You can try manually: systemctl --user enable --now copilot-proxy')\n return false\n }\n\n try {\n execSync(`loginctl enable-linger ${os.userInfo().username}`)\n }\n catch {\n consola.warn('Could not enable linger. Service may not run when logged out.')\n }\n\n consola.success('Auto-start enabled via systemd')\n return true\n}\n\nexport async function uninstallAutoStart(): Promise<boolean> {\n if (!fs.existsSync(SERVICE_PATH)) {\n consola.info('Auto-start service is not installed')\n return true\n }\n\n let hadErrors = false\n\n try {\n execSync(`systemctl --user stop ${SERVICE_NAME}`, { stdio: 'pipe' })\n }\n catch {\n // Service may not be running, that's fine\n }\n\n try {\n execSync(`systemctl --user disable ${SERVICE_NAME}`, { stdio: 'pipe' })\n }\n catch (error) {\n consola.warn('Failed to disable service:', error instanceof Error ? error.message : error)\n hadErrors = true\n }\n\n try {\n fs.unlinkSync(SERVICE_PATH)\n }\n catch (error: unknown) {\n if (error instanceof Error && 'code' in error && error.code !== 'ENOENT') {\n consola.error('Failed to remove service file:', error.message)\n return false\n }\n }\n\n try {\n execSync('systemctl --user daemon-reload', { stdio: 'pipe' })\n }\n catch (error) {\n consola.warn('Failed to reload systemd:', error instanceof Error ? error.message : error)\n hadErrors = true\n }\n\n if (hadErrors) {\n consola.warn('Auto-start disabled with warnings')\n return false\n }\n\n consola.success('Auto-start disabled')\n return true\n}\n"],"mappings":";;;;;;;;AAQA,MAAM,eAAe;AACrB,MAAM,cAAc,KAAK,KAAK,GAAG,SAAS,EAAE,WAAW,WAAW,OAAO;AACzE,MAAM,eAAe,KAAK,KAAK,aAAa,GAAG,aAAa,UAAU;AAEtE,SAAS,WAAW,GAAmB;CAErC,MAAM,UAAU,EAAE,QAAQ,MAAM,KAAK;AACrC,KAAI,cAAc,KAAK,QAAQ,CAC7B,QAAO;AACT,QAAO,IAAI,QAAQ,QAAQ,MAAM,OAAM,CAAC;;AAG1C,eAAsB,iBAAiB,UAAkB,MAAkC;AACzF,KAAI;AACF,WAAS,mBAAmB,EAAE,OAAO,QAAQ,CAAC;SAE1C;AACJ,UAAQ,MAAM,wDAAwD;AACtE,UAAQ,KAAK,sEAAsE;AACnF,SAAO;;CAGT,MAAM,OAAO;;;;;YAKH,WAAW,SAAS,CAAC,GAAG,KAAK,KAAI,MAAK,WAAW,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC;;;wBAGnD,MAAM,WAAW,QAAQ,MAAM,KAAK,CAAC;uBACtC,MAAM,WAAW,QAAQ,MAAM,KAAK,CAAC;;;;;AAM1D,IAAG,UAAU,aAAa,EAAE,WAAW,MAAM,CAAC;AAC9C,IAAG,cAAc,cAAc,KAAK;AAEpC,KAAI;AACF,WAAS,kCAAkC,EAAE,OAAO,QAAQ,CAAC;SAEzD;AACJ,UAAQ,MAAM,6DAA6D;AAC3E,UAAQ,KAAK,iGAAiG;AAC9G,SAAO;;AAGT,KAAI;AACF,WAAS,iCAAiC,gBAAgB,EAAE,OAAO,QAAQ,CAAC;UAEvE,OAAO;AACZ,UAAQ,MAAM,6BAA6B,iBAAiB,QAAQ,MAAM,UAAU,MAAM;AAC1F,UAAQ,KAAK,4BAA4B,eAAe;AACxD,UAAQ,KAAK,oEAAoE;AACjF,SAAO;;AAGT,KAAI;AACF,WAAS,0BAA0B,GAAG,UAAU,CAAC,WAAW;SAExD;AACJ,UAAQ,KAAK,gEAAgE;;AAG/E,SAAQ,QAAQ,iCAAiC;AACjD,QAAO;;AAGT,eAAsB,qBAAuC;AAC3D,KAAI,CAAC,GAAG,WAAW,aAAa,EAAE;AAChC,UAAQ,KAAK,sCAAsC;AACnD,SAAO;;CAGT,IAAI,YAAY;AAEhB,KAAI;AACF,WAAS,yBAAyB,gBAAgB,EAAE,OAAO,QAAQ,CAAC;SAEhE;AAIN,KAAI;AACF,WAAS,4BAA4B,gBAAgB,EAAE,OAAO,QAAQ,CAAC;UAElE,OAAO;AACZ,UAAQ,KAAK,8BAA8B,iBAAiB,QAAQ,MAAM,UAAU,MAAM;AAC1F,cAAY;;AAGd,KAAI;AACF,KAAG,WAAW,aAAa;UAEtBA,OAAgB;AACrB,MAAI,iBAAiB,SAAS,UAAU,SAAS,MAAM,SAAS,UAAU;AACxE,WAAQ,MAAM,kCAAkC,MAAM,QAAQ;AAC9D,UAAO;;;AAIX,KAAI;AACF,WAAS,kCAAkC,EAAE,OAAO,QAAQ,CAAC;UAExD,OAAO;AACZ,UAAQ,KAAK,6BAA6B,iBAAiB,QAAQ,MAAM,UAAU,MAAM;AACzF,cAAY;;AAGd,KAAI,WAAW;AACb,UAAQ,KAAK,oCAAoC;AACjD,SAAO;;AAGT,SAAQ,QAAQ,sBAAsB;AACtC,QAAO"}
|