ellm-proxy 0.0.3 → 0.0.7

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.
Files changed (58) hide show
  1. package/README.md +60 -60
  2. package/bin/commands/autostart.js +37 -213
  3. package/bin/commands/config.js +8 -8
  4. package/bin/commands/credentials.js +100 -0
  5. package/bin/commands/pm2.js +28 -28
  6. package/bin/commands/service.js +107 -107
  7. package/bin/ellm-proxy.js +64 -61
  8. package/dist/server.js +141 -4
  9. package/dist/web-ui/assets/DashboardView-BCwrFrE2.js +1 -0
  10. package/dist/web-ui/assets/DashboardView-BHG4xIdN.css +1 -0
  11. package/dist/web-ui/assets/LayoutView-BUA8-IzK.css +1 -0
  12. package/dist/web-ui/assets/LayoutView-BePJfgia.js +1 -0
  13. package/dist/web-ui/assets/LoginView-Dp7NmXtf.js +1 -0
  14. package/dist/web-ui/assets/LoginView-DrhfslIY.css +1 -0
  15. package/dist/web-ui/assets/{LogsView-B2ctrpv4.js → LogsView-Dyy_zkgg.js} +2 -2
  16. package/dist/web-ui/assets/ProcessView-CpPpMtFg.js +1 -0
  17. package/dist/web-ui/assets/ProcessView-DK0TvDpR.css +1 -0
  18. package/dist/web-ui/assets/{ProvidersView-mb0KU16n.js → ProvidersView-CYFs76wJ.js} +2 -2
  19. package/dist/web-ui/assets/SettingsView-BkwyoOUb.css +1 -0
  20. package/dist/web-ui/assets/SettingsView-mPcaVAD2.js +1 -0
  21. package/dist/web-ui/assets/SetupView-DX6S_KpK.js +1 -0
  22. package/dist/web-ui/assets/SetupView-kPJXJ7dX.css +1 -0
  23. package/dist/web-ui/assets/config-CRWaUFQc.js +1 -0
  24. package/dist/web-ui/assets/{index-dPeMJjrz.js → index-X5PdkJlA.js} +28 -28
  25. package/dist/web-ui/assets/{providers-Dmz2p4Wd.js → providers-H_9x778D.js} +1 -1
  26. package/dist/web-ui/assets/useManagerApi-C1_COxKg.js +1 -0
  27. package/dist/web-ui/assets/{useToast-B3quFZyv.js → useToast-MtbkGJiM.js} +1 -1
  28. package/dist/web-ui/config.js +10 -10
  29. package/dist/web-ui/index.html +13 -13
  30. package/docs/DESIGN.md +99 -99
  31. package/ellm.config.example.json +2 -0
  32. package/manager/api/harness/backup.js +20 -20
  33. package/manager/api/harness/claude-code.js +54 -54
  34. package/manager/api/harness/codex.js +123 -123
  35. package/manager/api/harness/dsh.js +175 -175
  36. package/manager/api/harness/index.js +118 -118
  37. package/manager/api/service-settings.js +98 -0
  38. package/manager/api/service.js +3 -13
  39. package/manager/index.js +182 -170
  40. package/package.json +4 -1
  41. package/utils/autostart.js +219 -0
  42. package/utils/interpreter.js +14 -14
  43. package/utils/paths.js +45 -44
  44. package/utils/pm2.js +130 -130
  45. package/utils/service.js +205 -189
  46. package/utils/updater.js +233 -0
  47. package/dist/web-ui/assets/DashboardView-B71Dy3px.js +0 -1
  48. package/dist/web-ui/assets/DashboardView-qrQrkgLA.css +0 -1
  49. package/dist/web-ui/assets/LayoutView-C03GVB3X.js +0 -1
  50. package/dist/web-ui/assets/LoginView-Dg0mB6ok.css +0 -1
  51. package/dist/web-ui/assets/LoginView-XTF5dHvd.js +0 -1
  52. package/dist/web-ui/assets/ProcessView-DmbVABQV.js +0 -1
  53. package/dist/web-ui/assets/ProcessView-DzMqBJmd.css +0 -1
  54. package/dist/web-ui/assets/SettingsView-BDkymsrS.css +0 -1
  55. package/dist/web-ui/assets/SettingsView-BgLQVC2H.js +0 -1
  56. package/dist/web-ui/assets/config-BRaXq0LL.js +0 -1
  57. package/dist/web-ui/assets/useApi-C1Ngt_o5.js +0 -1
  58. package/dist/web-ui/assets/useManagerApi-BDWJz5o6.js +0 -1
package/utils/paths.js CHANGED
@@ -1,44 +1,45 @@
1
- // 共享常量与路径工具:包根/应用目录常量、配置查找链、首次配置自愈、选项绝对化。
2
- // 供 CLI(bin/)与管理服务(manager/)两侧复用;工具一律返回数据,人读文案由调用方输出。
3
- import { readFileSync, mkdirSync, existsSync, copyFileSync } from "node:fs";
4
- import { homedir } from "node:os";
5
- import { dirname, join, resolve } from "node:path";
6
- import { fileURLToPath } from "node:url";
7
-
8
- /** 本文件在 utils/ 下,包根为上一级 */
9
- export const pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
10
- export const defaultAppDir = join(homedir(), ".ellm-proxy");
11
- export const pkg = JSON.parse(readFileSync(join(pkgRoot, "package.json"), "utf-8"));
12
- export const APP_NAME = "ellm-proxy"; // 主服务(dist/server.js)的 pm2 进程名
13
- export const MANAGER_APP_NAME = "ellm-proxy-manager"; // 管理服务(manager/index.js)的 pm2 进程名
14
- export const MIN_NODE = ">=22.13.0"; // node:sqlite 最低版(与包 engines 一致)
15
-
16
- /** 配置文件路径:--config > ELLM_CONFIG 环境变量 > ~/.ellm-proxy/ellm.config.json(与 server 端查找链一致) */
17
- export function configPath(override) {
18
- if (override) return resolve(override);
19
- return process.env["ELLM_CONFIG"] ? resolve(process.env["ELLM_CONFIG"]) : join(defaultAppDir, "ellm.config.json");
20
- }
21
-
22
- /** 首次配置自愈:不存在则从包内示例复制创建,并提示两种修改方式 */
23
- export function ensureConfig(override) {
24
- const config = configPath(override);
25
- if (existsSync(config)) return config;
26
- const example = join(pkgRoot, "ellm.config.example.json");
27
- if (!existsSync(example)) {
28
- throw new Error(`缺少示例配置 ${example},无法自动创建配置文件`);
29
- }
30
- mkdirSync(dirname(config), { recursive: true });
31
- copyFileSync(example, config);
32
- console.log(`已创建配置文件: ${config}`);
33
- console.log("可直接编辑该文件(填入供应商 apiKey),或经管理台在线修改(实时热更新)。");
34
- return config;
35
- }
36
-
37
- /** 路径类选项统一转绝对路径(相对按调用方当前目录展开;CLI cwd 与 node 进程工作目录基准不同,透传相对串会错位) */
38
- export function absolutize(opts) {
39
- const out = { ...opts };
40
- for (const key of ["config", "dataDir", "workingDir", "serverJs"]) {
41
- if (out[key]) out[key] = resolve(out[key]);
42
- }
43
- return out;
44
- }
1
+ // 共享常量与路径工具:包根/应用目录常量、配置查找链、首次配置自愈、选项绝对化。
2
+ // 供 CLI(bin/)与管理服务(manager/)两侧复用;工具一律返回数据,人读文案由调用方输出。
3
+ import { readFileSync, mkdirSync, existsSync, copyFileSync } from "node:fs";
4
+ import { homedir } from "node:os";
5
+ import { dirname, join, resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ /** 本文件在 utils/ 下,包根为上一级 */
9
+ export const pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
10
+ export const defaultAppDir = join(homedir(), ".ellm-proxy");
11
+ export const pkg = JSON.parse(readFileSync(join(pkgRoot, "package.json"), "utf-8"));
12
+ export const APP_NAME = "ellm-proxy"; // 主服务(dist/server.js)的 pm2 进程名
13
+ export const MANAGER_APP_NAME = "ellm-proxy-manager"; // 管理服务(manager/index.js)的 pm2 进程名
14
+ export const MIN_NODE = ">=22.13.0"; // node:sqlite 最低版(与包 engines 一致)
15
+ export const FALLBACK_NODE = "22"; // 兜底解释器锁定的主版本(满足 MIN_NODE,与 nve 22 口径一致)
16
+
17
+ /** 配置文件路径:--config > ELLM_CONFIG 环境变量 > ~/.ellm-proxy/ellm.config.json(与 server 端查找链一致) */
18
+ export function configPath(override) {
19
+ if (override) return resolve(override);
20
+ return process.env["ELLM_CONFIG"] ? resolve(process.env["ELLM_CONFIG"]) : join(defaultAppDir, "ellm.config.json");
21
+ }
22
+
23
+ /** 首次配置自愈:不存在则从包内示例复制创建,并提示两种修改方式 */
24
+ export function ensureConfig(override) {
25
+ const config = configPath(override);
26
+ if (existsSync(config)) return config;
27
+ const example = join(pkgRoot, "ellm.config.example.json");
28
+ if (!existsSync(example)) {
29
+ throw new Error(`缺少示例配置 ${example},无法自动创建配置文件`);
30
+ }
31
+ mkdirSync(dirname(config), { recursive: true });
32
+ copyFileSync(example, config);
33
+ console.log(`已创建配置文件: ${config}`);
34
+ console.log("可直接编辑该文件(填入供应商 apiKey),或经管理台在线修改(实时热更新)。");
35
+ return config;
36
+ }
37
+
38
+ /** 路径类选项统一转绝对路径(相对按调用方当前目录展开;CLI cwd 与 node 进程工作目录基准不同,透传相对串会错位) */
39
+ export function absolutize(opts) {
40
+ const out = { ...opts };
41
+ for (const key of ["config", "dataDir", "workingDir", "serverJs"]) {
42
+ if (out[key]) out[key] = resolve(out[key]);
43
+ }
44
+ return out;
45
+ }
package/utils/pm2.js CHANGED
@@ -1,130 +1,130 @@
1
- // pm2 JS API 封装:连接包裹/回调转 promise/应用查询/探活轮询/安全 dump/包内 pm2 CLI 定位/env 白名单。
2
- import { rmSync } from "node:fs";
3
- import { homedir } from "node:os";
4
- import { dirname, join } from "node:path";
5
- import { createRequire } from "node:module";
6
- import pm2 from "pm2";
7
- import { APP_NAME, MANAGER_APP_NAME } from "./paths.js";
8
- import { dot, endOk, endFail } from "./ui.js";
9
-
10
- /** pm2.connect 包裹:fn 内可调 pm2.xxx(回调式),结束后自动 disconnect */
11
- export function withPm2(fn) {
12
- return new Promise((resolvePromise, reject) => {
13
- pm2.connect((connectErr) => {
14
- if (connectErr) return reject(connectErr);
15
- Promise.resolve(fn(pm2)).then(
16
- (value) => pm2.disconnect(() => resolvePromise(value)),
17
- (err) => {
18
- try {
19
- pm2.disconnect();
20
- } catch {
21
- /* 已断开 */
22
- }
23
- reject(err);
24
- },
25
- );
26
- });
27
- });
28
- }
29
-
30
- /** pm2 回调式方法转 promise */
31
- export function pm2cb(method, ...args) {
32
- return new Promise((resolvePromise, reject) => {
33
- pm2[method](...args, (err, ...out) => (err ? reject(err) : resolvePromise(...out)));
34
- });
35
- }
36
-
37
- /** 查询应用当前进程记录(list 中按 name 匹配;不存在返回 undefined)。
38
- * name 缺省 = 主服务 ellm-proxy;管理服务传 MANAGER_APP_NAME */
39
- export async function findApp(name = APP_NAME) {
40
- const list = (await pm2cb("list")) || [];
41
- return list.find((p) => p.name === name);
42
- }
43
-
44
- /** 启动探活:轮询 status 至 online(每 pollMs 一次,全程打进度点)——比固定等待更快成功
45
- * (online 即返回),也能容忍慢启动(默认 15s 上限,固定 1.5s 在慢机器上会误报);errored/stopped
46
- * 提前失败(脚本秒崩不必等满超时)。failHint 是失败文案的线索部分(文案是契约的一部分)。
47
- * quiet = 静默轮询不打点(多服务并行等待时输出由调用方合并,避免多组进度点交错)。 */
48
- export async function waitOnline(name, failHint, { timeoutMs = 15000, pollMs = 300, quiet = false } = {}) {
49
- const deadline = Date.now() + timeoutMs;
50
- let last = null;
51
- for (;;) {
52
- last = await findApp(name);
53
- const status = last?.pm2_env?.status;
54
- if (status === "online") {
55
- if (!quiet) endOk();
56
- return last;
57
- }
58
- if (status === "errored" || status === "stopped" || Date.now() >= deadline) {
59
- if (!quiet) endFail();
60
- throw new Error(`${name} 启动后状态异常(${status ?? "已退出"})。${failHint}`);
61
- }
62
- if (!quiet) dot();
63
- await new Promise((r) => setTimeout(r, pollMs));
64
- }
65
- }
66
-
67
- /** 安全 dump(start/stop 成功后调用):pm2 不允许 dump 空列表会报错——此时说明全部进程已删,
68
- * 清掉旧 dump.pm2 与滚动备份 dump.pm2.bak(resurrect 无 dump.pm2 时会回落 .bak),防止开机
69
- * resurrect 把已停止的服务拉起来 */
70
- export async function safeDump() {
71
- try {
72
- await pm2cb("dump");
73
- } catch {
74
- try {
75
- rmSync(join(homedir(), ".pm2", "dump.pm2"), { force: true });
76
- rmSync(join(homedir(), ".pm2", "dump.pm2.bak"), { force: true });
77
- } catch {
78
- /* 清理失败不影响 stop 语义 */
79
- }
80
- }
81
- }
82
-
83
- /** 当前包内 pm2 CLI 入口绝对路径(autostart cmd 用;resolve 兼容 npm 全局平铺与 pnpm 布局) */
84
- export function pm2BinJs() {
85
- const pm2PkgRoot = dirname(createRequire(import.meta.url).resolve("pm2/package.json"));
86
- return join(pm2PkgRoot, "bin", "pm2");
87
- }
88
-
89
- /** 清洗 pm2 子进程透传的 env:pm2 托管的进程自身 env 带 pm_id/pm2_env/unique_id 等
90
- * 内部变量与 bash 注入的 `_`,原样传回 pm2.start 会污染新进程注册——实测主服务
91
- * (name=ellm-proxy)被注册成管理服务的名字,导致探活误判「已退出」。
92
- * 黑名单清洗实测仍有漏网(多键组合触发),故改用白名单:
93
- * 只透传主服务必需的业务键(ELLM_* 前缀,含 ELLM_PORT / ELLM_MANAGER_PORT)与 Windows node 运行所需的系统键。
94
- * PM2_HOME 保留(pm2 API 连接 daemon 需要,主服务自身不用)。 */
95
- const PM2_ENV_WHITELIST = new Set([
96
- "PATH",
97
- "SYSTEMROOT",
98
- "WINDIR",
99
- "SYSTEMDRIVE",
100
- "COMSPEC",
101
- "PATHEXT",
102
- "OS",
103
- "TEMP",
104
- "TMP",
105
- "USERPROFILE",
106
- "HOMEDRIVE",
107
- "HOMEPATH",
108
- "APPDATA",
109
- "LOCALAPPDATA",
110
- "PROGRAMDATA",
111
- "PROGRAMFILES",
112
- "ALLUSERSPROFILE",
113
- "PUBLIC",
114
- "USERNAME",
115
- "USERDOMAIN",
116
- "COMPUTERNAME",
117
- "NUMBER_OF_PROCESSORS",
118
- "HTTP_PROXY",
119
- "HTTPS_PROXY",
120
- "NO_PROXY",
121
- ]);
122
-
123
- export function sanitizePm2Env(env) {
124
- const out = {};
125
- for (const [key, value] of Object.entries(env)) {
126
- if (key === "PM2_HOME" || key.startsWith("ELLM_")) out[key] = value;
127
- else if (PM2_ENV_WHITELIST.has(key.toUpperCase())) out[key] = value;
128
- }
129
- return out;
130
- }
1
+ // pm2 JS API 封装:连接包裹/回调转 promise/应用查询/探活轮询/安全 dump/包内 pm2 CLI 定位/env 白名单。
2
+ import { rmSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join } from "node:path";
5
+ import { createRequire } from "node:module";
6
+ import pm2 from "pm2";
7
+ import { APP_NAME, MANAGER_APP_NAME } from "./paths.js";
8
+ import { dot, endOk, endFail } from "./ui.js";
9
+
10
+ /** pm2.connect 包裹:fn 内可调 pm2.xxx(回调式),结束后自动 disconnect */
11
+ export function withPm2(fn) {
12
+ return new Promise((resolvePromise, reject) => {
13
+ pm2.connect((connectErr) => {
14
+ if (connectErr) return reject(connectErr);
15
+ Promise.resolve(fn(pm2)).then(
16
+ (value) => pm2.disconnect(() => resolvePromise(value)),
17
+ (err) => {
18
+ try {
19
+ pm2.disconnect();
20
+ } catch {
21
+ /* 已断开 */
22
+ }
23
+ reject(err);
24
+ },
25
+ );
26
+ });
27
+ });
28
+ }
29
+
30
+ /** pm2 回调式方法转 promise */
31
+ export function pm2cb(method, ...args) {
32
+ return new Promise((resolvePromise, reject) => {
33
+ pm2[method](...args, (err, ...out) => (err ? reject(err) : resolvePromise(...out)));
34
+ });
35
+ }
36
+
37
+ /** 查询应用当前进程记录(list 中按 name 匹配;不存在返回 undefined)。
38
+ * name 缺省 = 主服务 ellm-proxy;管理服务传 MANAGER_APP_NAME */
39
+ export async function findApp(name = APP_NAME) {
40
+ const list = (await pm2cb("list")) || [];
41
+ return list.find((p) => p.name === name);
42
+ }
43
+
44
+ /** 启动探活:轮询 status 至 online(每 pollMs 一次,全程打进度点)——比固定等待更快成功
45
+ * (online 即返回),也能容忍慢启动(默认 15s 上限,固定 1.5s 在慢机器上会误报);errored/stopped
46
+ * 提前失败(脚本秒崩不必等满超时)。failHint 是失败文案的线索部分(文案是契约的一部分)。
47
+ * quiet = 静默轮询不打点(多服务并行等待时输出由调用方合并,避免多组进度点交错)。 */
48
+ export async function waitOnline(name, failHint, { timeoutMs = 15000, pollMs = 300, quiet = false } = {}) {
49
+ const deadline = Date.now() + timeoutMs;
50
+ let last = null;
51
+ for (;;) {
52
+ last = await findApp(name);
53
+ const status = last?.pm2_env?.status;
54
+ if (status === "online") {
55
+ if (!quiet) endOk();
56
+ return last;
57
+ }
58
+ if (status === "errored" || status === "stopped" || Date.now() >= deadline) {
59
+ if (!quiet) endFail();
60
+ throw new Error(`${name} 启动后状态异常(${status ?? "已退出"})。${failHint}`);
61
+ }
62
+ if (!quiet) dot();
63
+ await new Promise((r) => setTimeout(r, pollMs));
64
+ }
65
+ }
66
+
67
+ /** 安全 dump(start/stop 成功后调用):pm2 不允许 dump 空列表会报错——此时说明全部进程已删,
68
+ * 清掉旧 dump.pm2 与滚动备份 dump.pm2.bak(resurrect 无 dump.pm2 时会回落 .bak),防止开机
69
+ * resurrect 把已停止的服务拉起来 */
70
+ export async function safeDump() {
71
+ try {
72
+ await pm2cb("dump");
73
+ } catch {
74
+ try {
75
+ rmSync(join(homedir(), ".pm2", "dump.pm2"), { force: true });
76
+ rmSync(join(homedir(), ".pm2", "dump.pm2.bak"), { force: true });
77
+ } catch {
78
+ /* 清理失败不影响 stop 语义 */
79
+ }
80
+ }
81
+ }
82
+
83
+ /** 当前包内 pm2 CLI 入口绝对路径(autostart cmd 用;resolve 兼容 npm 全局平铺与 pnpm 布局) */
84
+ export function pm2BinJs() {
85
+ const pm2PkgRoot = dirname(createRequire(import.meta.url).resolve("pm2/package.json"));
86
+ return join(pm2PkgRoot, "bin", "pm2");
87
+ }
88
+
89
+ /** 清洗 pm2 子进程透传的 env:pm2 托管的进程自身 env 带 pm_id/pm2_env/unique_id 等
90
+ * 内部变量与 bash 注入的 `_`,原样传回 pm2.start 会污染新进程注册——实测主服务
91
+ * (name=ellm-proxy)被注册成管理服务的名字,导致探活误判「已退出」。
92
+ * 黑名单清洗实测仍有漏网(多键组合触发),故改用白名单:
93
+ * 只透传主服务必需的业务键(ELLM_* 前缀,含 ELLM_PORT / ELLM_MANAGER_PORT)与 Windows node 运行所需的系统键。
94
+ * PM2_HOME 保留(pm2 API 连接 daemon 需要,主服务自身不用)。 */
95
+ const PM2_ENV_WHITELIST = new Set([
96
+ "PATH",
97
+ "SYSTEMROOT",
98
+ "WINDIR",
99
+ "SYSTEMDRIVE",
100
+ "COMSPEC",
101
+ "PATHEXT",
102
+ "OS",
103
+ "TEMP",
104
+ "TMP",
105
+ "USERPROFILE",
106
+ "HOMEDRIVE",
107
+ "HOMEPATH",
108
+ "APPDATA",
109
+ "LOCALAPPDATA",
110
+ "PROGRAMDATA",
111
+ "PROGRAMFILES",
112
+ "ALLUSERSPROFILE",
113
+ "PUBLIC",
114
+ "USERNAME",
115
+ "USERDOMAIN",
116
+ "COMPUTERNAME",
117
+ "NUMBER_OF_PROCESSORS",
118
+ "HTTP_PROXY",
119
+ "HTTPS_PROXY",
120
+ "NO_PROXY",
121
+ ]);
122
+
123
+ export function sanitizePm2Env(env) {
124
+ const out = {};
125
+ for (const [key, value] of Object.entries(env)) {
126
+ if (key === "PM2_HOME" || key.startsWith("ELLM_")) out[key] = value;
127
+ else if (PM2_ENV_WHITELIST.has(key.toUpperCase())) out[key] = value;
128
+ }
129
+ return out;
130
+ }