ellm-proxy 0.0.3 → 0.0.6

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 (53) 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/pm2.js +28 -28
  5. package/bin/commands/service.js +107 -107
  6. package/bin/ellm-proxy.js +61 -61
  7. package/dist/server.js +33 -2
  8. package/dist/web-ui/assets/DashboardView-BHG4xIdN.css +1 -0
  9. package/dist/web-ui/assets/{DashboardView-B71Dy3px.js → DashboardView-C3Hw-HpG.js} +1 -1
  10. package/dist/web-ui/assets/LayoutView-BUA8-IzK.css +1 -0
  11. package/dist/web-ui/assets/LayoutView-C9phf7in.js +1 -0
  12. package/dist/web-ui/assets/LoginView-DrhfslIY.css +1 -0
  13. package/dist/web-ui/assets/LoginView-es6pSPgt.js +1 -0
  14. package/dist/web-ui/assets/{LogsView-B2ctrpv4.js → LogsView-BlXITEJH.js} +1 -1
  15. package/dist/web-ui/assets/ProcessView-Bgf7-MLQ.js +1 -0
  16. package/dist/web-ui/assets/ProcessView-DK0TvDpR.css +1 -0
  17. package/dist/web-ui/assets/{ProvidersView-mb0KU16n.js → ProvidersView-f8aZkMdw.js} +1 -1
  18. package/dist/web-ui/assets/SettingsView-BkwyoOUb.css +1 -0
  19. package/dist/web-ui/assets/SettingsView-GvAA2vLp.js +1 -0
  20. package/dist/web-ui/assets/config-Cs3_z63C.js +1 -0
  21. package/dist/web-ui/assets/{index-dPeMJjrz.js → index-GohB4JDa.js} +2 -2
  22. package/dist/web-ui/assets/{providers-Dmz2p4Wd.js → providers-Djtdy2tv.js} +1 -1
  23. package/dist/web-ui/assets/{useApi-C1Ngt_o5.js → useApi-D_iJ2Mca.js} +1 -1
  24. package/dist/web-ui/assets/useManagerApi-C1_COxKg.js +1 -0
  25. package/dist/web-ui/assets/{useToast-B3quFZyv.js → useToast-C0UMTbkx.js} +1 -1
  26. package/dist/web-ui/config.js +10 -10
  27. package/dist/web-ui/index.html +13 -13
  28. package/docs/DESIGN.md +99 -99
  29. package/manager/api/harness/backup.js +20 -20
  30. package/manager/api/harness/claude-code.js +54 -54
  31. package/manager/api/harness/codex.js +123 -123
  32. package/manager/api/harness/dsh.js +175 -175
  33. package/manager/api/harness/index.js +118 -118
  34. package/manager/api/service-settings.js +98 -0
  35. package/manager/api/service.js +3 -13
  36. package/manager/index.js +182 -170
  37. package/package.json +4 -1
  38. package/utils/autostart.js +219 -0
  39. package/utils/interpreter.js +14 -14
  40. package/utils/paths.js +45 -44
  41. package/utils/pm2.js +130 -130
  42. package/utils/service.js +205 -189
  43. package/utils/updater.js +210 -0
  44. package/dist/web-ui/assets/DashboardView-qrQrkgLA.css +0 -1
  45. package/dist/web-ui/assets/LayoutView-C03GVB3X.js +0 -1
  46. package/dist/web-ui/assets/LoginView-Dg0mB6ok.css +0 -1
  47. package/dist/web-ui/assets/LoginView-XTF5dHvd.js +0 -1
  48. package/dist/web-ui/assets/ProcessView-DmbVABQV.js +0 -1
  49. package/dist/web-ui/assets/ProcessView-DzMqBJmd.css +0 -1
  50. package/dist/web-ui/assets/SettingsView-BDkymsrS.css +0 -1
  51. package/dist/web-ui/assets/SettingsView-BgLQVC2H.js +0 -1
  52. package/dist/web-ui/assets/config-BRaXq0LL.js +0 -1
  53. package/dist/web-ui/assets/useManagerApi-BDWJz5o6.js +0 -1
@@ -1,54 +1,54 @@
1
- // Claude Code 代理开关:~/.claude/settings.json 的 env.ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN
2
- // 指向本机 ellm-proxy 网关。JSON 字段级修改,其余字段原样保留。
3
- // 开关状态以实际文件内容判定(env.ANTHROPIC_BASE_URL 是否已指向网关);不落额外状态文件。
4
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
5
- import { homedir } from "node:os";
6
- import { join, dirname } from "node:path";
7
- import { backupOnce, restoreFromBackup } from "./backup.js";
8
-
9
- const CLAUDE_SETTINGS = join(homedir(), ".claude", "settings.json");
10
-
11
- /** 去尾斜杠比较,容忍 http://x:1/ 与 http://x:1 视为同一地址 */
12
- function sameUrl(a, b) {
13
- return String(a || "").replace(/\/+$/, "") === String(b || "").replace(/\/+$/, "");
14
- }
15
-
16
- function readClaudeSettings() {
17
- if (!existsSync(CLAUDE_SETTINGS)) return null;
18
- try {
19
- return JSON.parse(readFileSync(CLAUDE_SETTINGS, "utf-8"));
20
- } catch (err) {
21
- throw new Error(`${CLAUDE_SETTINGS} 不是合法 JSON,拒绝修改(请先手工修复): ${err.message}`);
22
- }
23
- }
24
-
25
- /** Claude Code 开关状态:env.ANTHROPIC_BASE_URL 是否已指向网关 */
26
- export function claudeCodeStatus(gatewayBase) {
27
- const settings = readClaudeSettings();
28
- if (!settings) return false;
29
- return sameUrl(settings?.env?.ANTHROPIC_BASE_URL, gatewayBase);
30
- }
31
-
32
- /** 开启 Claude Code 代理:备份 → 字段级写入 env.ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN(其余字段原样保留) */
33
- export function claudeCodeEnable({ gatewayBase, token }) {
34
- mkdirSync(dirname(CLAUDE_SETTINGS), { recursive: true });
35
- const settings = readClaudeSettings() ?? {};
36
- backupOnce(CLAUDE_SETTINGS);
37
- settings.env = { ...(settings.env ?? {}) };
38
- settings.env.ANTHROPIC_BASE_URL = gatewayBase;
39
- settings.env.ANTHROPIC_AUTH_TOKEN = token;
40
- writeFileSync(CLAUDE_SETTINGS, JSON.stringify(settings, null, 2) + "\n", "utf-8");
41
- }
42
-
43
- /** 关闭 Claude Code 代理:从备份还原(无备份时仅移除代理相关字段) */
44
- export function claudeCodeDisable(gatewayBase) {
45
- if (restoreFromBackup(CLAUDE_SETTINGS)) return true;
46
- const settings = readClaudeSettings();
47
- if (!settings) return false;
48
- if (!sameUrl(settings?.env?.ANTHROPIC_BASE_URL, gatewayBase)) return false;
49
- delete settings.env.ANTHROPIC_BASE_URL;
50
- delete settings.env.ANTHROPIC_AUTH_TOKEN;
51
- if (settings.env && Object.keys(settings.env).length === 0) delete settings.env;
52
- writeFileSync(CLAUDE_SETTINGS, JSON.stringify(settings, null, 2) + "\n", "utf-8");
53
- return true;
54
- }
1
+ // Claude Code 代理开关:~/.claude/settings.json 的 env.ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN
2
+ // 指向本机 ellm-proxy 网关。JSON 字段级修改,其余字段原样保留。
3
+ // 开关状态以实际文件内容判定(env.ANTHROPIC_BASE_URL 是否已指向网关);不落额外状态文件。
4
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
5
+ import { homedir } from "node:os";
6
+ import { join, dirname } from "node:path";
7
+ import { backupOnce, restoreFromBackup } from "./backup.js";
8
+
9
+ const CLAUDE_SETTINGS = join(homedir(), ".claude", "settings.json");
10
+
11
+ /** 去尾斜杠比较,容忍 http://x:1/ 与 http://x:1 视为同一地址 */
12
+ function sameUrl(a, b) {
13
+ return String(a || "").replace(/\/+$/, "") === String(b || "").replace(/\/+$/, "");
14
+ }
15
+
16
+ function readClaudeSettings() {
17
+ if (!existsSync(CLAUDE_SETTINGS)) return null;
18
+ try {
19
+ return JSON.parse(readFileSync(CLAUDE_SETTINGS, "utf-8"));
20
+ } catch (err) {
21
+ throw new Error(`${CLAUDE_SETTINGS} 不是合法 JSON,拒绝修改(请先手工修复): ${err.message}`);
22
+ }
23
+ }
24
+
25
+ /** Claude Code 开关状态:env.ANTHROPIC_BASE_URL 是否已指向网关 */
26
+ export function claudeCodeStatus(gatewayBase) {
27
+ const settings = readClaudeSettings();
28
+ if (!settings) return false;
29
+ return sameUrl(settings?.env?.ANTHROPIC_BASE_URL, gatewayBase);
30
+ }
31
+
32
+ /** 开启 Claude Code 代理:备份 → 字段级写入 env.ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN(其余字段原样保留) */
33
+ export function claudeCodeEnable({ gatewayBase, token }) {
34
+ mkdirSync(dirname(CLAUDE_SETTINGS), { recursive: true });
35
+ const settings = readClaudeSettings() ?? {};
36
+ backupOnce(CLAUDE_SETTINGS);
37
+ settings.env = { ...(settings.env ?? {}) };
38
+ settings.env.ANTHROPIC_BASE_URL = gatewayBase;
39
+ settings.env.ANTHROPIC_AUTH_TOKEN = token;
40
+ writeFileSync(CLAUDE_SETTINGS, JSON.stringify(settings, null, 2) + "\n", "utf-8");
41
+ }
42
+
43
+ /** 关闭 Claude Code 代理:从备份还原(无备份时仅移除代理相关字段) */
44
+ export function claudeCodeDisable(gatewayBase) {
45
+ if (restoreFromBackup(CLAUDE_SETTINGS)) return true;
46
+ const settings = readClaudeSettings();
47
+ if (!settings) return false;
48
+ if (!sameUrl(settings?.env?.ANTHROPIC_BASE_URL, gatewayBase)) return false;
49
+ delete settings.env.ANTHROPIC_BASE_URL;
50
+ delete settings.env.ANTHROPIC_AUTH_TOKEN;
51
+ if (settings.env && Object.keys(settings.env).length === 0) delete settings.env;
52
+ writeFileSync(CLAUDE_SETTINGS, JSON.stringify(settings, null, 2) + "\n", "utf-8");
53
+ return true;
54
+ }
@@ -1,123 +1,123 @@
1
- // Codex 代理开关:~/.codex/config.toml 顶层 model_provider + [model_providers.ellm-proxy] 段
2
- // 指向本机 ellm-proxy 网关(语法见 openai/codex docs/config.md;wire_api 仅支持 chat|responses,
3
- // 此处用 chat 走 /v1/chat/completions)。
4
- // TOML 解析/校验用 smol-toml 库(不手写解析器);写入采用文本级编辑以保留用户注释与排版,
5
- // 且写盘前必须通过库校验(结果文本可解析且 model_provider 正确),否则抛错保留原文件。
6
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
7
- import { homedir } from "node:os";
8
- import { join, dirname } from "node:path";
9
- import { parse as parseToml } from "smol-toml";
10
- import { backupOnce, restoreFromBackup } from "./backup.js";
11
-
12
- const PROVIDER_ID = "ellm-proxy"; // Codex provider id(避开官方保留 id: openai/ollama/lmstudio)
13
- const CODEX_CONFIG = join(homedir(), ".codex", "config.toml");
14
-
15
- function readCodexConfig() {
16
- return existsSync(CODEX_CONFIG) ? readFileSync(CODEX_CONFIG, "utf-8") : null;
17
- }
18
-
19
- /** 是否已写顶层 model_provider = "ellm-proxy"(库解析;文本不合法按未开启处理,enable 时会给出明确报错) */
20
- function codexProviderActive(text) {
21
- try {
22
- return parseToml(text)?.model_provider === PROVIDER_ID;
23
- } catch {
24
- return false;
25
- }
26
- }
27
-
28
- /** 库校验:文本必须可解析,且 expectProvider 为 true 时顶层 model_provider 必须是本 provider(写盘前强制调用) */
29
- function validateTomlText(text, expectProvider) {
30
- let parsed;
31
- try {
32
- parsed = parseToml(text);
33
- } catch (err) {
34
- throw new Error(`编辑后的 config.toml 不是合法 TOML,已保留原文件: ${err.message}`);
35
- }
36
- if (expectProvider && parsed?.model_provider !== PROVIDER_ID) {
37
- throw new Error(`编辑后的 config.toml 顶层 model_provider 不是 "${PROVIDER_ID}",已保留原文件`);
38
- }
39
- }
40
-
41
- /** Codex 开关状态:顶层 model_provider 是否为 ellm-proxy */
42
- export function codexStatus() {
43
- const text = readCodexConfig();
44
- return text ? codexProviderActive(text) : false;
45
- }
46
-
47
- /** 移除已有的 [model_providers.ellm-proxy] 段(从段头到下一个段头/文件尾),供重写为最新 base_url */
48
- function removeProviderSection(text) {
49
- const lines = text.split("\n");
50
- const out = [];
51
- let skipping = false;
52
- for (const line of lines) {
53
- if (/^\s*\[model_providers\.ellm-proxy\]\s*$/.test(line)) {
54
- skipping = true;
55
- continue;
56
- }
57
- if (skipping && /^\s*\[/.test(line)) skipping = false; // 下一个段头,恢复收集
58
- if (!skipping) out.push(line);
59
- }
60
- return out.join("\n").replace(/\n{3,}$/, "\n\n");
61
- }
62
-
63
- /** 设置顶层 model_provider = "ellm-proxy":已有该键则替换(忽略注释行),否则插到首个段头前(TOML 顶层键必须在任何 [section] 之前) */
64
- function setTopLevelProvider(text) {
65
- const lines = text.split("\n");
66
- const keyLine = `model_provider = "${PROVIDER_ID}"`;
67
- let replaced = false;
68
- const out = lines.map((line) => {
69
- if (!replaced && /^\s*model_provider\s*=/.test(line) && !line.trim().startsWith("#")) {
70
- replaced = true;
71
- return keyLine;
72
- }
73
- return line;
74
- });
75
- if (replaced) return out.join("\n");
76
- const firstSection = out.findIndex((line) => /^\s*\[/.test(line));
77
- if (firstSection === -1) return [...out, keyLine].join("\n");
78
- out.splice(firstSection, 0, keyLine, "");
79
- return out.join("\n");
80
- }
81
-
82
- /** 开启 Codex 代理:备份 → 写 model_provider + [model_providers.ellm-proxy] 段(重复开启幂等刷新 base_url)。
83
- * 原文本不合法时直接报错(不覆盖坏文件);编辑结果写盘前经库校验 */
84
- export function codexEnable({ gatewayBase }) {
85
- mkdirSync(dirname(CODEX_CONFIG), { recursive: true });
86
- const original = readCodexConfig();
87
- if (original !== null) {
88
- try {
89
- parseToml(original);
90
- } catch (err) {
91
- throw new Error(`${CODEX_CONFIG} 不是合法 TOML,拒绝修改(请先手工修复): ${err.message}`);
92
- }
93
- }
94
- let text = original ?? "";
95
- backupOnce(CODEX_CONFIG);
96
- text = removeProviderSection(text);
97
- text = setTopLevelProvider(text);
98
- const section = [
99
- `[model_providers.${PROVIDER_ID}]`,
100
- `name = "ellm-proxy gateway"`,
101
- `base_url = "${gatewayBase}/v1"`,
102
- `wire_api = "chat"`,
103
- `env_key = "ELLM_PROXY_API_KEY"`,
104
- ].join("\n");
105
- text = text.replace(/\s*$/, "") + "\n\n" + section + "\n";
106
- validateTomlText(text, true);
107
- writeFileSync(CODEX_CONFIG, text, "utf-8");
108
- }
109
-
110
- /** 关闭 Codex 代理:从备份还原(无备份时移除 provider 段与顶层键,写盘前经库校验) */
111
- export function codexDisable() {
112
- if (restoreFromBackup(CODEX_CONFIG)) return true;
113
- const text = readCodexConfig();
114
- if (!text || !codexProviderActive(text)) return false;
115
- let out = removeProviderSection(text);
116
- out = out
117
- .split("\n")
118
- .filter((line) => !/^\s*model_provider\s*=\s*["']?ellm-proxy["']?\s*$/.test(line))
119
- .join("\n");
120
- validateTomlText(out, false);
121
- writeFileSync(CODEX_CONFIG, out, "utf-8");
122
- return true;
123
- }
1
+ // Codex 代理开关:~/.codex/config.toml 顶层 model_provider + [model_providers.ellm-proxy] 段
2
+ // 指向本机 ellm-proxy 网关(语法见 openai/codex docs/config.md;wire_api 仅支持 chat|responses,
3
+ // 此处用 chat 走 /v1/chat/completions)。
4
+ // TOML 解析/校验用 smol-toml 库(不手写解析器);写入采用文本级编辑以保留用户注释与排版,
5
+ // 且写盘前必须通过库校验(结果文本可解析且 model_provider 正确),否则抛错保留原文件。
6
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
7
+ import { homedir } from "node:os";
8
+ import { join, dirname } from "node:path";
9
+ import { parse as parseToml } from "smol-toml";
10
+ import { backupOnce, restoreFromBackup } from "./backup.js";
11
+
12
+ const PROVIDER_ID = "ellm-proxy"; // Codex provider id(避开官方保留 id: openai/ollama/lmstudio)
13
+ const CODEX_CONFIG = join(homedir(), ".codex", "config.toml");
14
+
15
+ function readCodexConfig() {
16
+ return existsSync(CODEX_CONFIG) ? readFileSync(CODEX_CONFIG, "utf-8") : null;
17
+ }
18
+
19
+ /** 是否已写顶层 model_provider = "ellm-proxy"(库解析;文本不合法按未开启处理,enable 时会给出明确报错) */
20
+ function codexProviderActive(text) {
21
+ try {
22
+ return parseToml(text)?.model_provider === PROVIDER_ID;
23
+ } catch {
24
+ return false;
25
+ }
26
+ }
27
+
28
+ /** 库校验:文本必须可解析,且 expectProvider 为 true 时顶层 model_provider 必须是本 provider(写盘前强制调用) */
29
+ function validateTomlText(text, expectProvider) {
30
+ let parsed;
31
+ try {
32
+ parsed = parseToml(text);
33
+ } catch (err) {
34
+ throw new Error(`编辑后的 config.toml 不是合法 TOML,已保留原文件: ${err.message}`);
35
+ }
36
+ if (expectProvider && parsed?.model_provider !== PROVIDER_ID) {
37
+ throw new Error(`编辑后的 config.toml 顶层 model_provider 不是 "${PROVIDER_ID}",已保留原文件`);
38
+ }
39
+ }
40
+
41
+ /** Codex 开关状态:顶层 model_provider 是否为 ellm-proxy */
42
+ export function codexStatus() {
43
+ const text = readCodexConfig();
44
+ return text ? codexProviderActive(text) : false;
45
+ }
46
+
47
+ /** 移除已有的 [model_providers.ellm-proxy] 段(从段头到下一个段头/文件尾),供重写为最新 base_url */
48
+ function removeProviderSection(text) {
49
+ const lines = text.split("\n");
50
+ const out = [];
51
+ let skipping = false;
52
+ for (const line of lines) {
53
+ if (/^\s*\[model_providers\.ellm-proxy\]\s*$/.test(line)) {
54
+ skipping = true;
55
+ continue;
56
+ }
57
+ if (skipping && /^\s*\[/.test(line)) skipping = false; // 下一个段头,恢复收集
58
+ if (!skipping) out.push(line);
59
+ }
60
+ return out.join("\n").replace(/\n{3,}$/, "\n\n");
61
+ }
62
+
63
+ /** 设置顶层 model_provider = "ellm-proxy":已有该键则替换(忽略注释行),否则插到首个段头前(TOML 顶层键必须在任何 [section] 之前) */
64
+ function setTopLevelProvider(text) {
65
+ const lines = text.split("\n");
66
+ const keyLine = `model_provider = "${PROVIDER_ID}"`;
67
+ let replaced = false;
68
+ const out = lines.map((line) => {
69
+ if (!replaced && /^\s*model_provider\s*=/.test(line) && !line.trim().startsWith("#")) {
70
+ replaced = true;
71
+ return keyLine;
72
+ }
73
+ return line;
74
+ });
75
+ if (replaced) return out.join("\n");
76
+ const firstSection = out.findIndex((line) => /^\s*\[/.test(line));
77
+ if (firstSection === -1) return [...out, keyLine].join("\n");
78
+ out.splice(firstSection, 0, keyLine, "");
79
+ return out.join("\n");
80
+ }
81
+
82
+ /** 开启 Codex 代理:备份 → 写 model_provider + [model_providers.ellm-proxy] 段(重复开启幂等刷新 base_url)。
83
+ * 原文本不合法时直接报错(不覆盖坏文件);编辑结果写盘前经库校验 */
84
+ export function codexEnable({ gatewayBase }) {
85
+ mkdirSync(dirname(CODEX_CONFIG), { recursive: true });
86
+ const original = readCodexConfig();
87
+ if (original !== null) {
88
+ try {
89
+ parseToml(original);
90
+ } catch (err) {
91
+ throw new Error(`${CODEX_CONFIG} 不是合法 TOML,拒绝修改(请先手工修复): ${err.message}`);
92
+ }
93
+ }
94
+ let text = original ?? "";
95
+ backupOnce(CODEX_CONFIG);
96
+ text = removeProviderSection(text);
97
+ text = setTopLevelProvider(text);
98
+ const section = [
99
+ `[model_providers.${PROVIDER_ID}]`,
100
+ `name = "ellm-proxy gateway"`,
101
+ `base_url = "${gatewayBase}/v1"`,
102
+ `wire_api = "chat"`,
103
+ `env_key = "ELLM_PROXY_API_KEY"`,
104
+ ].join("\n");
105
+ text = text.replace(/\s*$/, "") + "\n\n" + section + "\n";
106
+ validateTomlText(text, true);
107
+ writeFileSync(CODEX_CONFIG, text, "utf-8");
108
+ }
109
+
110
+ /** 关闭 Codex 代理:从备份还原(无备份时移除 provider 段与顶层键,写盘前经库校验) */
111
+ export function codexDisable() {
112
+ if (restoreFromBackup(CODEX_CONFIG)) return true;
113
+ const text = readCodexConfig();
114
+ if (!text || !codexProviderActive(text)) return false;
115
+ let out = removeProviderSection(text);
116
+ out = out
117
+ .split("\n")
118
+ .filter((line) => !/^\s*model_provider\s*=\s*["']?ellm-proxy["']?\s*$/.test(line))
119
+ .join("\n");
120
+ validateTomlText(out, false);
121
+ writeFileSync(CODEX_CONFIG, out, "utf-8");
122
+ return true;
123
+ }