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
@@ -1,118 +1,118 @@
1
- // harness 代理开关聚合:Claude Code / Codex CLI / DeepSeek Harness 流量指向本机 ellm-proxy 网关。
2
- // 纯开关实现:claude-code.js(~/.claude/settings.json)/ codex.js(~/.codex/config.toml)/
3
- // dsh.js(~/.dsh/settings.yaml + ~/.dsh/.credentials.yaml)/ backup.js(共用备份还原)
4
- // createHarnessApp = HTTP 路由子应用(挂载于 /harness):状态查询 + 开关切换(含 masterKey 校验与提示文案)
5
- import { Hono } from "hono";
6
- import { readFileSync } from "node:fs";
7
- import { resolveGatewayBase } from "../../../utils/service.js";
8
- import { claudeCodeStatus, claudeCodeEnable, claudeCodeDisable } from "./claude-code.js";
9
- import { codexStatus, codexEnable, codexDisable } from "./codex.js";
10
- import { dshStatus, dshEnable, dshDisable } from "./dsh.js";
11
-
12
- /** 配置文件顶层 masterKey(Claude Code 代理的 AUTH_TOKEN 取值;网关侧用它做鉴权) */
13
- function readMasterKey(configFile) {
14
- try {
15
- return JSON.parse(readFileSync(configFile, "utf-8")).masterKey || "";
16
- } catch {
17
- return "";
18
- }
19
- }
20
-
21
- /** harness 开关子应用(挂载于 /harness):GET / · POST /claude-code · POST /codex · POST /dsh(body {enabled}) */
22
- export function createHarnessApp({ startOpts }) {
23
- const app = new Hono();
24
-
25
- // GET /harness:三个开关状态
26
- app.get("/", (c) => {
27
- const { base } = resolveGatewayBase(startOpts.config);
28
- return c.json({ ok: true, claudeCode: claudeCodeStatus(base), codex: codexStatus(), dsh: dshStatus(base), gatewayBase: base });
29
- });
30
-
31
- /** 读 body 并校验 enabled;合法返回 boolean,非法返回 400 Response(调用方直接 return 该响应) */
32
- const requireEnabled = async (c) => {
33
- let enabled;
34
- try {
35
- enabled = (await c.req.json())?.enabled;
36
- } catch {
37
- return c.json({ ok: false, error: "请求体不是合法 JSON" }, 400);
38
- }
39
- if (typeof enabled !== "boolean") {
40
- return c.json({ ok: false, error: 'body 需为 {"enabled": true|false}' }, 400);
41
- }
42
- return enabled;
43
- };
44
-
45
- app.post("/claude-code", async (c) => {
46
- const enabled = await requireEnabled(c);
47
- if (enabled instanceof Response) return enabled;
48
- const { base, configFile } = resolveGatewayBase(startOpts.config);
49
- if (enabled) {
50
- const masterKey = readMasterKey(configFile);
51
- if (!masterKey) {
52
- return c.json(
53
- {
54
- ok: false,
55
- error: `配置未设置 masterKey,Claude Code 代理无法鉴权。请先在配置文件中设置 masterKey: ${configFile}`,
56
- },
57
- 400,
58
- );
59
- }
60
- claudeCodeEnable({ gatewayBase: base, token: masterKey });
61
- return c.json({
62
- ok: true,
63
- claudeCode: true,
64
- file: "~/.claude/settings.json",
65
- note: "已备份原文件为 settings.json.ellm-bak,重启 Claude Code 会话后生效",
66
- });
67
- }
68
- const changed = claudeCodeDisable(base);
69
- return c.json({ ok: true, claudeCode: false, changed, note: changed ? "已从备份还原" : "原本未开启,无操作" });
70
- });
71
-
72
- app.post("/codex", async (c) => {
73
- const enabled = await requireEnabled(c);
74
- if (enabled instanceof Response) return enabled;
75
- const { base, configFile } = resolveGatewayBase(startOpts.config);
76
- if (enabled) {
77
- codexEnable({ gatewayBase: base });
78
- const masterKey = readMasterKey(configFile);
79
- return c.json({
80
- ok: true,
81
- codex: true,
82
- file: "~/.codex/config.toml",
83
- note: masterKey
84
- ? `已备份原文件为 config.toml.ellm-bak;请设置环境变量 ELLM_PROXY_API_KEY=${masterKey}(Codex 以 env_key 读取鉴权)`
85
- : "已备份原文件为 config.toml.ellm-bak;请设置环境变量 ELLM_PROXY_API_KEY(网关未配 masterKey 时取值任意)",
86
- });
87
- }
88
- const changed = codexDisable();
89
- return c.json({ ok: true, codex: false, changed, note: changed ? "已从备份还原" : "原本未开启,无操作" });
90
- });
91
-
92
- app.post("/dsh", async (c) => {
93
- const enabled = await requireEnabled(c);
94
- if (enabled instanceof Response) return enabled;
95
- const { base, configFile } = resolveGatewayBase(startOpts.config);
96
- const masterKey = readMasterKey(configFile);
97
- if (enabled) {
98
- dshEnable({ gatewayBase: base, masterKey });
99
- return c.json({
100
- ok: true,
101
- dsh: true,
102
- file: "~/.dsh/settings.yaml",
103
- note: masterKey
104
- ? "已写入 settings.yaml(llm-deepseek.baseURL)与 .credentials.yaml(refs.DEEPSEEK_API_KEY),原文件备份为 *.ellm-bak。注意:进程环境里已设 DEEPSEEK_API_KEY 时其优先级更高,需自行改用网关 masterKey;模型请在 dsh 内选择与网关部署名一致的模型"
105
- : "已写入 settings.yaml 与 .credentials.yaml(网关未配 masterKey,密钥为占位值即可放行),原文件备份为 *.ellm-bak。模型请在 dsh 内选择与网关部署名一致的模型",
106
- });
107
- }
108
- const changed = dshDisable(base, masterKey);
109
- return c.json({ ok: true, dsh: false, changed, note: changed ? "已从备份还原(或移除本开关写入的字段)" : "原本未开启,无操作" });
110
- });
111
-
112
- return app;
113
- }
114
-
115
- // 纯开关函数同时聚合导出(供不经过 HTTP 的调用方直接使用)
116
- export * from "./claude-code.js";
117
- export * from "./codex.js";
118
- export * from "./dsh.js";
1
+ // harness 代理开关聚合:Claude Code / Codex CLI / DeepSeek Harness 流量指向本机 ellm-proxy 网关。
2
+ // 纯开关实现:claude-code.js(~/.claude/settings.json)/ codex.js(~/.codex/config.toml)/
3
+ // dsh.js(~/.dsh/settings.yaml + ~/.dsh/.credentials.yaml)/ backup.js(共用备份还原)
4
+ // createHarnessApp = HTTP 路由子应用(挂载于 /harness):状态查询 + 开关切换(含 masterKey 校验与提示文案)
5
+ import { Hono } from "hono";
6
+ import { readFileSync } from "node:fs";
7
+ import { resolveGatewayBase } from "../../../utils/service.js";
8
+ import { claudeCodeStatus, claudeCodeEnable, claudeCodeDisable } from "./claude-code.js";
9
+ import { codexStatus, codexEnable, codexDisable } from "./codex.js";
10
+ import { dshStatus, dshEnable, dshDisable } from "./dsh.js";
11
+
12
+ /** 配置文件顶层 masterKey(Claude Code 代理的 AUTH_TOKEN 取值;网关侧用它做鉴权) */
13
+ function readMasterKey(configFile) {
14
+ try {
15
+ return JSON.parse(readFileSync(configFile, "utf-8")).masterKey || "";
16
+ } catch {
17
+ return "";
18
+ }
19
+ }
20
+
21
+ /** harness 开关子应用(挂载于 /harness):GET / · POST /claude-code · POST /codex · POST /dsh(body {enabled}) */
22
+ export function createHarnessApp({ startOpts }) {
23
+ const app = new Hono();
24
+
25
+ // GET /harness:三个开关状态
26
+ app.get("/", (c) => {
27
+ const { base } = resolveGatewayBase(startOpts.config);
28
+ return c.json({ ok: true, claudeCode: claudeCodeStatus(base), codex: codexStatus(), dsh: dshStatus(base), gatewayBase: base });
29
+ });
30
+
31
+ /** 读 body 并校验 enabled;合法返回 boolean,非法返回 400 Response(调用方直接 return 该响应) */
32
+ const requireEnabled = async (c) => {
33
+ let enabled;
34
+ try {
35
+ enabled = (await c.req.json())?.enabled;
36
+ } catch {
37
+ return c.json({ ok: false, error: "请求体不是合法 JSON" }, 400);
38
+ }
39
+ if (typeof enabled !== "boolean") {
40
+ return c.json({ ok: false, error: 'body 需为 {"enabled": true|false}' }, 400);
41
+ }
42
+ return enabled;
43
+ };
44
+
45
+ app.post("/claude-code", async (c) => {
46
+ const enabled = await requireEnabled(c);
47
+ if (enabled instanceof Response) return enabled;
48
+ const { base, configFile } = resolveGatewayBase(startOpts.config);
49
+ if (enabled) {
50
+ const masterKey = readMasterKey(configFile);
51
+ if (!masterKey) {
52
+ return c.json(
53
+ {
54
+ ok: false,
55
+ error: `配置未设置 masterKey,Claude Code 代理无法鉴权。请先在配置文件中设置 masterKey: ${configFile}`,
56
+ },
57
+ 400,
58
+ );
59
+ }
60
+ claudeCodeEnable({ gatewayBase: base, token: masterKey });
61
+ return c.json({
62
+ ok: true,
63
+ claudeCode: true,
64
+ file: "~/.claude/settings.json",
65
+ note: "已备份原文件为 settings.json.ellm-bak,重启 Claude Code 会话后生效",
66
+ });
67
+ }
68
+ const changed = claudeCodeDisable(base);
69
+ return c.json({ ok: true, claudeCode: false, changed, note: changed ? "已从备份还原" : "原本未开启,无操作" });
70
+ });
71
+
72
+ app.post("/codex", async (c) => {
73
+ const enabled = await requireEnabled(c);
74
+ if (enabled instanceof Response) return enabled;
75
+ const { base, configFile } = resolveGatewayBase(startOpts.config);
76
+ if (enabled) {
77
+ codexEnable({ gatewayBase: base });
78
+ const masterKey = readMasterKey(configFile);
79
+ return c.json({
80
+ ok: true,
81
+ codex: true,
82
+ file: "~/.codex/config.toml",
83
+ note: masterKey
84
+ ? `已备份原文件为 config.toml.ellm-bak;请设置环境变量 ELLM_PROXY_API_KEY=${masterKey}(Codex 以 env_key 读取鉴权)`
85
+ : "已备份原文件为 config.toml.ellm-bak;请设置环境变量 ELLM_PROXY_API_KEY(网关未配 masterKey 时取值任意)",
86
+ });
87
+ }
88
+ const changed = codexDisable();
89
+ return c.json({ ok: true, codex: false, changed, note: changed ? "已从备份还原" : "原本未开启,无操作" });
90
+ });
91
+
92
+ app.post("/dsh", async (c) => {
93
+ const enabled = await requireEnabled(c);
94
+ if (enabled instanceof Response) return enabled;
95
+ const { base, configFile } = resolveGatewayBase(startOpts.config);
96
+ const masterKey = readMasterKey(configFile);
97
+ if (enabled) {
98
+ dshEnable({ gatewayBase: base, masterKey });
99
+ return c.json({
100
+ ok: true,
101
+ dsh: true,
102
+ file: "~/.dsh/settings.yaml",
103
+ note: masterKey
104
+ ? "已写入 settings.yaml(llm-deepseek.baseURL)与 .credentials.yaml(refs.DEEPSEEK_API_KEY),原文件备份为 *.ellm-bak。注意:进程环境里已设 DEEPSEEK_API_KEY 时其优先级更高,需自行改用网关 masterKey;模型请在 dsh 内选择与网关部署名一致的模型"
105
+ : "已写入 settings.yaml 与 .credentials.yaml(网关未配 masterKey,密钥为占位值即可放行),原文件备份为 *.ellm-bak。模型请在 dsh 内选择与网关部署名一致的模型",
106
+ });
107
+ }
108
+ const changed = dshDisable(base, masterKey);
109
+ return c.json({ ok: true, dsh: false, changed, note: changed ? "已从备份还原(或移除本开关写入的字段)" : "原本未开启,无操作" });
110
+ });
111
+
112
+ return app;
113
+ }
114
+
115
+ // 纯开关函数同时聚合导出(供不经过 HTTP 的调用方直接使用)
116
+ export * from "./claude-code.js";
117
+ export * from "./codex.js";
118
+ export * from "./dsh.js";
@@ -0,0 +1,98 @@
1
+ // 服务设置 api 子应用:开机自启开关 + 自动更新(检查/开关/手动触发)。
2
+ // 与 api/service.js(进程控制)、api/harness/(代理开关)平级的第三域,路由形状/响应约定一致
3
+ // ({ok,...} 或 {ok:false,error});实现在 utils/autostart.js 与 utils/updater.js(CLI 侧复用)。
4
+ import { Hono } from "hono";
5
+ import { autostartStatus, enableAutostart, disableAutostart } from "../../utils/autostart.js";
6
+ import {
7
+ readUpdaterSettings,
8
+ writeUpdaterSettings,
9
+ checkForUpdate,
10
+ performUpdate,
11
+ isUpdateSupported,
12
+ isUpdating,
13
+ } from "../../utils/updater.js";
14
+
15
+ /** 读 body 并校验 enabled;合法返回 boolean,非法返回 400 Response(调用方直接 return 该响应) */
16
+ async function requireEnabled(c) {
17
+ let enabled;
18
+ try {
19
+ enabled = (await c.req.json())?.enabled;
20
+ } catch {
21
+ return c.json({ ok: false, error: "请求体不是合法 JSON" }, 400);
22
+ }
23
+ if (typeof enabled !== "boolean") {
24
+ return c.json({ ok: false, error: 'body 需为 {"enabled": true|false}' }, 400);
25
+ }
26
+ return enabled;
27
+ }
28
+
29
+ /** 服务设置子应用:GET/POST /autostart · GET /update · POST /update/enabled|check|apply */
30
+ export function createServiceSettingsApp({ startOpts }) {
31
+ const app = new Hono();
32
+
33
+ // —— 开机自启 ——————————————————————————————————————————————
34
+ app.get("/autostart", async (c) => c.json({ ok: true, ...(await autostartStatus()) }));
35
+
36
+ app.post("/autostart", async (c) => {
37
+ const enabled = await requireEnabled(c);
38
+ if (enabled instanceof Response) return enabled;
39
+ if (enabled) {
40
+ const { itemPath } = await enableAutostart();
41
+ return c.json({ ok: true, enabled: true, itemPath, note: "已注册自启项,下次登录时 pm2 resurrect 恢复服务" });
42
+ }
43
+ const { removed } = await disableAutostart();
44
+ return c.json({
45
+ ok: true,
46
+ enabled: false,
47
+ removed,
48
+ note: removed ? "已移除自启项,且首次启动不再自动注册(重新开启即可解除)" : "原本未开启,无操作",
49
+ });
50
+ });
51
+
52
+ // —— 自动更新 ——————————————————————————————————————————————
53
+ app.get("/update", (c) => {
54
+ const settings = readUpdaterSettings();
55
+ return c.json({
56
+ ok: true,
57
+ enabled: settings.enabled,
58
+ lastCheck: settings.lastCheck,
59
+ lastApply: settings.lastApply,
60
+ supported: isUpdateSupported(),
61
+ updating: isUpdating(),
62
+ });
63
+ });
64
+
65
+ app.post("/update/enabled", async (c) => {
66
+ const enabled = await requireEnabled(c);
67
+ if (enabled instanceof Response) return enabled;
68
+ writeUpdaterSettings({ enabled });
69
+ return c.json({
70
+ ok: true,
71
+ enabled,
72
+ note: enabled
73
+ ? "自动更新已开启(看门狗随下次 manager 启动生效;当前周期内手动检查不受影响)"
74
+ : "自动更新已关闭(看门狗在下次 tick 时停止)",
75
+ });
76
+ });
77
+
78
+ app.post("/update/check", async (c) => {
79
+ const result = await checkForUpdate();
80
+ return c.json({ ok: true, ...result });
81
+ });
82
+
83
+ app.post("/update/apply", async (c) => {
84
+ if (!isUpdateSupported()) {
85
+ return c.json({ ok: false, error: "当前为源码运行模式,更新功能不可用(仅 npm 全局安装的包支持自动更新)" }, 400);
86
+ }
87
+ const result = await performUpdate(startOpts);
88
+ return c.json({
89
+ ok: true,
90
+ ...result,
91
+ note: result.updated
92
+ ? `已更新 ${result.fromVersion} → ${result.toVersion}:主服务已重启,管理服务将在数秒后自重启(期间界面请求可能瞬时失败)`
93
+ : `已是最新版本 ${result.toVersion}`,
94
+ });
95
+ });
96
+
97
+ return app;
98
+ }
@@ -9,7 +9,7 @@ import { createRequire } from "node:module";
9
9
  import { Hono } from "hono";
10
10
  import { APP_NAME, MANAGER_APP_NAME } from "../../utils/paths.js";
11
11
  import { withPm2, findApp, pm2cb, safeDump } from "../../utils/pm2.js";
12
- import { doStart, resolveGatewayBase, resolvePort } from "../../utils/service.js";
12
+ import { doStart, stopMainOnly, resolveGatewayBase, resolvePort } from "../../utils/service.js";
13
13
 
14
14
  // 本文件在 manager/api/ 下,包根为上两级
15
15
  const pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
@@ -108,19 +108,9 @@ async function processesPayload(startOpts) {
108
108
  ];
109
109
  }
110
110
 
111
- /** 仅停止主服务(管理服务保持存活);管理服务仍在 dump 列表中,resurrect 后它会先起来继续提供远程启停 */
112
- async function stopMainOnly() {
113
- return withPm2(async () => {
114
- const proc = await findApp();
115
- if (!proc) return "already-stopped";
116
- await pm2cb("delete", APP_NAME);
117
- await safeDump();
118
- return "stopped";
119
- });
120
- }
121
-
122
111
  /** pm2 启停:start / stop / restart(restart = 停主服务 + start,
123
- * start 时管理服务已在线会被 doStart 的幂等检查跳过) */
112
+ * start 时管理服务已在线会被 doStart 的幂等检查跳过)。
113
+ * stopMainOnly 已抽到 utils/service.js(updater 更新前停主服务复用同语义)。 */
124
114
  async function runStartStop(action, startOpts) {
125
115
  if (action === "stop") return stopMainOnly();
126
116
  if (action === "start") return doStart(startOpts);