@wu529778790/open-im 1.10.5 → 1.10.6-beta.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 +11 -6
- package/README.zh-CN.md +11 -6
- package/dist/adapters/codex-adapter.js +1 -1
- package/dist/codebuddy/cli-runner.js +2 -5
- package/dist/codex/cli-runner.js +2 -5
- package/dist/config/file-io.d.ts +7 -2
- package/dist/config/file-io.js +76 -30
- package/dist/config/types.d.ts +0 -1
- package/dist/config-web-page-i18n.d.ts +8 -1
- package/dist/config-web-page-i18n.js +8 -1
- package/dist/config-web.js +78 -2
- package/dist/config.d.ts +1 -1
- package/dist/config.js +4 -16
- package/dist/setup.js +13 -23
- package/package.json +1 -1
- package/web/dist/assets/index-BuYsYEMc.js +57 -0
- package/web/dist/index.html +1 -1
- package/web/dist/assets/index-DYBizIKK.js +0 -55
package/README.md
CHANGED
|
@@ -75,16 +75,21 @@ Default: root **`aiCommand`**. Override with **`platforms.<name>.aiCommand`**:
|
|
|
75
75
|
|
|
76
76
|
### Claude (Agent SDK)
|
|
77
77
|
|
|
78
|
-
No local `claude` binary required. Credentials: env → **`config.json`**
|
|
78
|
+
No local `claude` binary required. Credentials: env → **`config.json`** **`tools.claude.env`** → **`~/.claude/settings.json`** (dashboard saves API fields here).
|
|
79
79
|
|
|
80
80
|
Third-party / compatible API example:
|
|
81
81
|
|
|
82
82
|
```json
|
|
83
83
|
{
|
|
84
|
-
"
|
|
85
|
-
"
|
|
86
|
-
|
|
87
|
-
|
|
84
|
+
"tools": {
|
|
85
|
+
"claude": {
|
|
86
|
+
"workDir": "/path/to/project",
|
|
87
|
+
"env": {
|
|
88
|
+
"ANTHROPIC_AUTH_TOKEN": "your-token",
|
|
89
|
+
"ANTHROPIC_BASE_URL": "https://your-api-endpoint",
|
|
90
|
+
"ANTHROPIC_MODEL": "glm-4.7"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
88
93
|
}
|
|
89
94
|
}
|
|
90
95
|
```
|
|
@@ -114,7 +119,7 @@ Add Feishu, QQ, WeCom, DingTalk, WorkBuddy under **`platforms`** as needed. Run
|
|
|
114
119
|
|
|
115
120
|
### Environment variables
|
|
116
121
|
|
|
117
|
-
Use **`config.json`** or environment variables; the dashboard exposes common options. Typical keys: **`ANTHROPIC_
|
|
122
|
+
Use **`config.json`** (platforms, `tools.*`, etc.) or environment variables; the dashboard exposes common options. Typical keys: **`ANTHROPIC_*`** (shell or **`tools.claude.env`**), **`TELEGRAM_BOT_TOKEN`**, **`OPEN_IM_WEB_PORT`**, **`OPEN_IM_WEB_HOST`**, plus platform-specific `*_APP_ID`, `*_SECRET`, `WORKBUDDY_*`, etc. The root-level **`config.json` `env`** field is no longer read.
|
|
118
123
|
|
|
119
124
|
### Privacy
|
|
120
125
|
|
package/README.zh-CN.md
CHANGED
|
@@ -75,16 +75,21 @@ npx @wu529778790/open-im start
|
|
|
75
75
|
|
|
76
76
|
### Claude(Agent SDK)
|
|
77
77
|
|
|
78
|
-
无需本地 `claude` 可执行文件。凭证顺序:环境变量 → **`config.json`** 的 **`env`** → **`~/.claude/settings.json
|
|
78
|
+
无需本地 `claude` 可执行文件。凭证顺序:环境变量 → **`config.json`** 的 **`tools.claude.env`** → **`~/.claude/settings.json`**(控制台保存的 API 配置写入后者)。
|
|
79
79
|
|
|
80
80
|
第三方兼容接口示例:
|
|
81
81
|
|
|
82
82
|
```json
|
|
83
83
|
{
|
|
84
|
-
"
|
|
85
|
-
"
|
|
86
|
-
|
|
87
|
-
|
|
84
|
+
"tools": {
|
|
85
|
+
"claude": {
|
|
86
|
+
"workDir": "/path/to/project",
|
|
87
|
+
"env": {
|
|
88
|
+
"ANTHROPIC_AUTH_TOKEN": "your-token",
|
|
89
|
+
"ANTHROPIC_BASE_URL": "https://your-api-endpoint",
|
|
90
|
+
"ANTHROPIC_MODEL": "glm-4.7"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
88
93
|
}
|
|
89
94
|
}
|
|
90
95
|
```
|
|
@@ -114,7 +119,7 @@ codebuddy login
|
|
|
114
119
|
|
|
115
120
|
### 环境变量
|
|
116
121
|
|
|
117
|
-
可在 **`config.json
|
|
122
|
+
可在 **`config.json`**(平台与 `tools.*` 等)或环境变量中设置;控制台会展示常用项。常见:**`ANTHROPIC_*`**(shell 或 **`tools.claude.env`**)、**`TELEGRAM_BOT_TOKEN`**、**`OPEN_IM_WEB_PORT`**、**`OPEN_IM_WEB_HOST`**,以及各平台的 `*_APP_ID`、`*_SECRET`、`WORKBUDDY_*` 等。根级 **`config.json` `env`** 已不再使用。
|
|
118
123
|
|
|
119
124
|
### 隐私
|
|
120
125
|
|
|
@@ -37,7 +37,7 @@ export class CodexAdapter {
|
|
|
37
37
|
onError: (err) => {
|
|
38
38
|
const msg = typeof err === "string" ? err : String(err);
|
|
39
39
|
const friendly = msg.includes("Authentication") || msg.includes("login")
|
|
40
|
-
? "Codex 需要先登录。请在终端运行 codex login,或在
|
|
40
|
+
? "Codex 需要先登录。请在终端运行 codex login,或在 shell 中 export OPENAI_API_KEY。"
|
|
41
41
|
: msg.includes("stream disconnected") ||
|
|
42
42
|
msg.includes("error sending request") ||
|
|
43
43
|
msg.includes("Connection refused") ||
|
|
@@ -2,6 +2,7 @@ import { execFileSync, spawn } from 'node:child_process';
|
|
|
2
2
|
import { accessSync, constants } from 'node:fs';
|
|
3
3
|
import { isAbsolute, join } from 'node:path';
|
|
4
4
|
import { createLogger } from '../logger.js';
|
|
5
|
+
import { processEnvForNonClaudeCliChild } from '../config/file-io.js';
|
|
5
6
|
const log = createLogger('CodeBuddyCli');
|
|
6
7
|
export function buildCodeBuddyArgs(prompt, sessionId, options) {
|
|
7
8
|
const args = ['--print', '--output-format', 'stream-json'];
|
|
@@ -164,11 +165,7 @@ export function runCodeBuddy(cliPath, prompt, sessionId, workDir, callbacks, opt
|
|
|
164
165
|
...options,
|
|
165
166
|
permissionMode: normalizePermissionMode(options?.permissionMode),
|
|
166
167
|
});
|
|
167
|
-
const env =
|
|
168
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
169
|
-
if (value !== undefined)
|
|
170
|
-
env[key] = value;
|
|
171
|
-
}
|
|
168
|
+
const env = processEnvForNonClaudeCliChild();
|
|
172
169
|
if (process.platform === 'win32') {
|
|
173
170
|
env.LANG = env.LANG || 'C.UTF-8';
|
|
174
171
|
env.LC_ALL = env.LC_ALL || 'C.UTF-8';
|
package/dist/codex/cli-runner.js
CHANGED
|
@@ -6,6 +6,7 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
6
6
|
import { dirname, join } from 'node:path';
|
|
7
7
|
import { createInterface } from 'node:readline';
|
|
8
8
|
import { createLogger } from '../logger.js';
|
|
9
|
+
import { processEnvForNonClaudeCliChild } from '../config/file-io.js';
|
|
9
10
|
const log = createLogger('CodexCli');
|
|
10
11
|
const windowsCodexLaunchCache = new Map();
|
|
11
12
|
const SUPPORTED_IMAGE_EXTENSIONS = new Set([
|
|
@@ -161,11 +162,7 @@ function resolveWindowsCodexLaunch(cliPath, args) {
|
|
|
161
162
|
}
|
|
162
163
|
export function runCodex(cliPath, prompt, sessionId, workDir, callbacks, options) {
|
|
163
164
|
const args = buildCodexArgs(prompt, sessionId, workDir, options);
|
|
164
|
-
const env =
|
|
165
|
-
for (const [k, v] of Object.entries(process.env)) {
|
|
166
|
-
if (v !== undefined)
|
|
167
|
-
env[k] = v;
|
|
168
|
-
}
|
|
165
|
+
const env = processEnvForNonClaudeCliChild();
|
|
169
166
|
if (options?.chatId)
|
|
170
167
|
env.CC_IM_CHAT_ID = options.chatId;
|
|
171
168
|
if (options?.hookPort)
|
package/dist/config/file-io.d.ts
CHANGED
|
@@ -3,6 +3,10 @@ export declare const CONFIG_PATH: string;
|
|
|
3
3
|
export declare const CODEX_AUTH_PATHS: string[];
|
|
4
4
|
/** Claude 认证相关的环境变量 key 列表 */
|
|
5
5
|
export declare const CLAUDE_AUTH_ENV_KEYS: readonly ["ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN", "CLAUDE_CODE_OAUTH_TOKEN", "ANTHROPIC_BASE_URL", "ANTHROPIC_MODEL"];
|
|
6
|
+
/**
|
|
7
|
+
* 供非 Claude CLI 子进程使用的环境:拷贝当前 process.env,并移除 Anthropic/Claude 专用项。
|
|
8
|
+
*/
|
|
9
|
+
export declare function processEnvForNonClaudeCliChild(): Record<string, string>;
|
|
6
10
|
export declare function loadFileConfig(): FileConfig;
|
|
7
11
|
export declare function saveFileConfig(raw: FileConfig): void;
|
|
8
12
|
export declare function getClaudeConfigHome(): string;
|
|
@@ -13,7 +17,8 @@ export declare function hasCodexAuth(): boolean;
|
|
|
13
17
|
export declare function parseCommaSeparated(value: string): string[];
|
|
14
18
|
/**
|
|
15
19
|
* 将最新的 Claude 认证环境变量按优先级合并到 process.env。
|
|
16
|
-
* 优先级:shell 环境变量 > tools.claude.env
|
|
17
|
-
*
|
|
20
|
+
* 优先级:shell 环境变量 > ~/.open-im/config.json 的 tools.claude.env >
|
|
21
|
+
* 本机 Claude 配置(~/.claude/settings.json,与 Claude Code 共用)。
|
|
22
|
+
* 多数用户只维护本机 settings;每次创建 Claude SDK 会话前调用,本机文件变更后下次会话即生效。
|
|
18
23
|
*/
|
|
19
24
|
export declare function refreshClaudeEnvToProcess(): void;
|
package/dist/config/file-io.js
CHANGED
|
@@ -24,6 +24,29 @@ export const CLAUDE_AUTH_ENV_KEYS = [
|
|
|
24
24
|
'ANTHROPIC_BASE_URL',
|
|
25
25
|
'ANTHROPIC_MODEL',
|
|
26
26
|
];
|
|
27
|
+
/**
|
|
28
|
+
* 不应传入 Codex / CodeBuddy 等子进程的环境变量。
|
|
29
|
+
* Claude 适配器会通过 refreshClaudeEnvToProcess 把这些写入 process.env;
|
|
30
|
+
* 若原样拷贝给 CLI 子进程,可能导致错误使用 ANTHROPIC_BASE_URL 等「访问地址」。
|
|
31
|
+
*/
|
|
32
|
+
const NON_CLAUDE_CLI_STRIP_KEYS = new Set([
|
|
33
|
+
...CLAUDE_AUTH_ENV_KEYS,
|
|
34
|
+
'ANTHROPIC_DEFAULT_HAIKU_MODEL',
|
|
35
|
+
'ANTHROPIC_DEFAULT_SONNET_MODEL',
|
|
36
|
+
'ANTHROPIC_DEFAULT_OPUS_MODEL',
|
|
37
|
+
]);
|
|
38
|
+
/**
|
|
39
|
+
* 供非 Claude CLI 子进程使用的环境:拷贝当前 process.env,并移除 Anthropic/Claude 专用项。
|
|
40
|
+
*/
|
|
41
|
+
export function processEnvForNonClaudeCliChild() {
|
|
42
|
+
const env = {};
|
|
43
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
44
|
+
if (v === undefined || NON_CLAUDE_CLI_STRIP_KEYS.has(k))
|
|
45
|
+
continue;
|
|
46
|
+
env[k] = v;
|
|
47
|
+
}
|
|
48
|
+
return env;
|
|
49
|
+
}
|
|
27
50
|
// Config cache with mtime tracking
|
|
28
51
|
let cachedConfig = null;
|
|
29
52
|
let cachedClaudeEnv = null;
|
|
@@ -109,39 +132,61 @@ export function saveFileConfig(raw) {
|
|
|
109
132
|
export function getClaudeConfigHome() {
|
|
110
133
|
return process.env.HOME || process.env.USERPROFILE || homedir();
|
|
111
134
|
}
|
|
135
|
+
function claudeSettingsJsonPath(home) {
|
|
136
|
+
return join(home, '.claude', 'settings.json');
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* 从单个 Claude settings JSON 根对象解析 env(与 Claude Code 行为对齐)。
|
|
140
|
+
* - `env` 对象内字段优先于根上同名认证键
|
|
141
|
+
* - 根上可存在 ANTHROPIC_* / CLAUDE_CODE_OAUTH_TOKEN(部分用户或旧版会写在顶层)
|
|
142
|
+
*/
|
|
143
|
+
function extractAuthEnvFromClaudeSettingsRoot(raw) {
|
|
144
|
+
const out = {};
|
|
145
|
+
for (const key of CLAUDE_AUTH_ENV_KEYS) {
|
|
146
|
+
const v = raw[key];
|
|
147
|
+
if (v != null && typeof v === 'string' && v.length > 0) {
|
|
148
|
+
out[key] = v;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const env = raw.env;
|
|
152
|
+
if (env && typeof env === 'object' && !Array.isArray(env)) {
|
|
153
|
+
for (const [k, v] of Object.entries(env)) {
|
|
154
|
+
if (v != null && typeof k === 'string') {
|
|
155
|
+
out[k] = String(v);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
112
161
|
export function loadClaudeSettingsEnv() {
|
|
113
162
|
const home = getClaudeConfigHome();
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
try {
|
|
120
|
-
if (existsSync(p)) {
|
|
121
|
-
const stats = statSync(p);
|
|
122
|
-
const currentMtime = stats.mtimeMs;
|
|
123
|
-
if (cachedClaudeEnv && cachedClaudeEnv.mtime === currentMtime && cachedClaudeEnv.env) {
|
|
124
|
-
return cachedClaudeEnv.env;
|
|
125
|
-
}
|
|
126
|
-
const raw = JSON.parse(readFileSync(p, 'utf-8'));
|
|
127
|
-
const env = raw?.env;
|
|
128
|
-
if (env && typeof env === 'object') {
|
|
129
|
-
const result = {};
|
|
130
|
-
for (const [k, v] of Object.entries(env)) {
|
|
131
|
-
if (v != null && typeof k === 'string') {
|
|
132
|
-
result[k] = String(v);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
cachedClaudeEnv = { env: result, mtime: currentMtime };
|
|
136
|
-
return result;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
163
|
+
const p = claudeSettingsJsonPath(home);
|
|
164
|
+
let fingerprint = '';
|
|
165
|
+
try {
|
|
166
|
+
if (existsSync(p)) {
|
|
167
|
+
fingerprint = `${p}:${statSync(p).mtimeMs}`;
|
|
139
168
|
}
|
|
140
|
-
|
|
141
|
-
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
/* ignore */
|
|
172
|
+
}
|
|
173
|
+
if (cachedClaudeEnv && cachedClaudeEnv.fingerprint === fingerprint) {
|
|
174
|
+
return cachedClaudeEnv.env;
|
|
175
|
+
}
|
|
176
|
+
let merged = {};
|
|
177
|
+
try {
|
|
178
|
+
if (existsSync(p)) {
|
|
179
|
+
const raw = JSON.parse(readFileSync(p, 'utf-8'));
|
|
180
|
+
if (raw && typeof raw === 'object') {
|
|
181
|
+
merged = extractAuthEnvFromClaudeSettingsRoot(raw);
|
|
182
|
+
}
|
|
142
183
|
}
|
|
143
184
|
}
|
|
144
|
-
|
|
185
|
+
catch {
|
|
186
|
+
/* 文件损坏或不可读 */
|
|
187
|
+
}
|
|
188
|
+
cachedClaudeEnv = { env: merged, fingerprint };
|
|
189
|
+
return merged;
|
|
145
190
|
}
|
|
146
191
|
export function saveClaudeSettingsEnv(env) {
|
|
147
192
|
const home = getClaudeConfigHome();
|
|
@@ -191,8 +236,9 @@ export function parseCommaSeparated(value) {
|
|
|
191
236
|
}
|
|
192
237
|
/**
|
|
193
238
|
* 将最新的 Claude 认证环境变量按优先级合并到 process.env。
|
|
194
|
-
* 优先级:shell 环境变量 > tools.claude.env
|
|
195
|
-
*
|
|
239
|
+
* 优先级:shell 环境变量 > ~/.open-im/config.json 的 tools.claude.env >
|
|
240
|
+
* 本机 Claude 配置(~/.claude/settings.json,与 Claude Code 共用)。
|
|
241
|
+
* 多数用户只维护本机 settings;每次创建 Claude SDK 会话前调用,本机文件变更后下次会话即生效。
|
|
196
242
|
*/
|
|
197
243
|
export function refreshClaudeEnvToProcess() {
|
|
198
244
|
const file = loadFileConfig();
|
package/dist/config/types.d.ts
CHANGED
|
@@ -99,6 +99,8 @@ export declare const PAGE_TEXTS: {
|
|
|
99
99
|
readonly codexCli: "Codex CLI path";
|
|
100
100
|
readonly codebuddyCli: "CodeBuddy CLI path";
|
|
101
101
|
readonly codexProxy: "Codex proxy";
|
|
102
|
+
readonly codexApiKey: "OPENAI_API_KEY";
|
|
103
|
+
readonly codexApiKeyTip: "Set the OpenAI API key used by Codex. You can also edit the auth file below.";
|
|
102
104
|
readonly claudeConfigPath: "Config file location";
|
|
103
105
|
readonly claudeAuthToken: "ANTHROPIC_AUTH_TOKEN";
|
|
104
106
|
readonly claudeBaseUrl: "ANTHROPIC_BASE_URL";
|
|
@@ -129,8 +131,9 @@ export declare const PAGE_TEXTS: {
|
|
|
129
131
|
readonly configEditorTitle: "Config Editor";
|
|
130
132
|
readonly configEditorHint: "Edit ~/.open-im/config.json directly";
|
|
131
133
|
readonly claudeSettingsLabel: "~/.claude/settings.json";
|
|
134
|
+
readonly codexSettingsLabel: "~/.codex/auth.json";
|
|
135
|
+
readonly codexSettingsCardHint: "Codex CLI auth (OPENAI_API_KEY etc.). Set API access here, or use the OPENAI_API_KEY field in the Codex tool tab.";
|
|
132
136
|
readonly configJson: "~/.open-im/config.json";
|
|
133
|
-
readonly configJsonHint: "Edit the configuration JSON. Changes will be saved when you click \"Save Config\" in the Service section.";
|
|
134
137
|
readonly formatJson: "Format";
|
|
135
138
|
readonly resetJson: "Reset";
|
|
136
139
|
readonly saveBtn: "Save";
|
|
@@ -248,6 +251,8 @@ export declare const PAGE_TEXTS: {
|
|
|
248
251
|
readonly codexCli: "Codex CLI 路径";
|
|
249
252
|
readonly codebuddyCli: "CodeBuddy CLI 路径";
|
|
250
253
|
readonly codexProxy: "Codex 代理";
|
|
254
|
+
readonly codexApiKey: "OPENAI_API_KEY";
|
|
255
|
+
readonly codexApiKeyTip: "设置 Codex 使用的 OpenAI API Key。也可以在下方编辑 auth 文件。";
|
|
251
256
|
readonly claudeConfigPath: "配置文件位置";
|
|
252
257
|
readonly claudeProxy: "代理(可选)";
|
|
253
258
|
readonly hookPort: "Hook 端口";
|
|
@@ -275,6 +280,8 @@ export declare const PAGE_TEXTS: {
|
|
|
275
280
|
readonly configEditorTitle: "JSON 配置编辑器";
|
|
276
281
|
readonly configEditorHint: "直接编辑 ~/.open-im/config.json";
|
|
277
282
|
readonly claudeSettingsLabel: "~/.claude/settings.json";
|
|
283
|
+
readonly codexSettingsLabel: "~/.codex/auth.json";
|
|
284
|
+
readonly codexSettingsCardHint: "Codex CLI 认证信息(OPENAI_API_KEY 等)。在此配置 API 访问,或在 Codex 工具标签页填写 OPENAI_API_KEY。";
|
|
278
285
|
readonly configJson: "~/.open-im/config.json";
|
|
279
286
|
readonly configJsonHint: "编辑配置 JSON。点击服务区的“保存配置”按钮后侘存更改。";
|
|
280
287
|
readonly formatJson: "格式化";
|
|
@@ -99,6 +99,8 @@ export const PAGE_TEXTS = {
|
|
|
99
99
|
codexCli: "Codex CLI path",
|
|
100
100
|
codebuddyCli: "CodeBuddy CLI path",
|
|
101
101
|
codexProxy: "Codex proxy",
|
|
102
|
+
codexApiKey: "OPENAI_API_KEY",
|
|
103
|
+
codexApiKeyTip: "Set the OpenAI API key used by Codex. You can also edit the auth file below.",
|
|
102
104
|
claudeConfigPath: "Config file location",
|
|
103
105
|
claudeAuthToken: "ANTHROPIC_AUTH_TOKEN",
|
|
104
106
|
claudeBaseUrl: "ANTHROPIC_BASE_URL",
|
|
@@ -129,8 +131,9 @@ export const PAGE_TEXTS = {
|
|
|
129
131
|
configEditorTitle: "Config Editor",
|
|
130
132
|
configEditorHint: "Edit ~/.open-im/config.json directly",
|
|
131
133
|
claudeSettingsLabel: "~/.claude/settings.json",
|
|
134
|
+
codexSettingsLabel: "~/.codex/auth.json",
|
|
135
|
+
codexSettingsCardHint: "Codex CLI auth (OPENAI_API_KEY etc.). Set API access here, or use the OPENAI_API_KEY field in the Codex tool tab.",
|
|
132
136
|
configJson: "~/.open-im/config.json",
|
|
133
|
-
configJsonHint: "Edit the configuration JSON. Changes will be saved when you click \"Save Config\" in the Service section.",
|
|
134
137
|
formatJson: "Format",
|
|
135
138
|
resetJson: "Reset",
|
|
136
139
|
saveBtn: "Save",
|
|
@@ -248,6 +251,8 @@ export const PAGE_TEXTS = {
|
|
|
248
251
|
codexCli: "Codex CLI \u8def\u5f84",
|
|
249
252
|
codebuddyCli: "CodeBuddy CLI \u8def\u5f84",
|
|
250
253
|
codexProxy: "Codex \u4ee3\u7406",
|
|
254
|
+
codexApiKey: "OPENAI_API_KEY",
|
|
255
|
+
codexApiKeyTip: "\u8bbe\u7f6e Codex \u4f7f\u7528\u7684 OpenAI API Key\u3002\u4e5f\u53ef\u4ee5\u5728\u4e0b\u65b9\u7f16\u8f91 auth \u6587\u4ef6\u3002",
|
|
251
256
|
claudeConfigPath: "\u914d\u7f6e\u6587\u4ef6\u4f4d\u7f6e",
|
|
252
257
|
claudeProxy: "\u4ee3\u7406\uff08\u53ef\u9009\uff09",
|
|
253
258
|
hookPort: "Hook \u7aef\u53e3",
|
|
@@ -275,6 +280,8 @@ export const PAGE_TEXTS = {
|
|
|
275
280
|
configEditorTitle: "JSON \u914d\u7f6e\u7f16\u8f91\u5668",
|
|
276
281
|
configEditorHint: "\u76f4\u63a5\u7f16\u8f91 ~/.open-im/config.json",
|
|
277
282
|
claudeSettingsLabel: "~/.claude/settings.json",
|
|
283
|
+
codexSettingsLabel: "~/.codex/auth.json",
|
|
284
|
+
codexSettingsCardHint: "Codex CLI \u8ba4\u8bc1\u4fe1\u606f\uff08OPENAI_API_KEY \u7b49\uff09\u3002\u5728\u6b64\u914d\u7f6e API \u8bbf\u95ee\uff0c\u6216\u5728 Codex \u5de5\u5177\u6807\u7b7e\u9875\u586b\u5199 OPENAI_API_KEY\u3002",
|
|
278
285
|
configJson: "~/.open-im/config.json",
|
|
279
286
|
configJsonHint: "\u7f16\u8f91\u914d\u7f6e JSON\u3002\u70b9\u51fb\u670d\u52a1\u533a\u7684\u201c\u4fdd\u5b58\u914d\u7f6e\u201d\u6309\u94ae\u540e\u4f98\u5b58\u66f4\u6539\u3002",
|
|
280
287
|
formatJson: "\u683c\u5f0f\u5316",
|
package/dist/config-web.js
CHANGED
|
@@ -6,7 +6,7 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
|
6
6
|
import { join, dirname } from "node:path";
|
|
7
7
|
import { DWClient } from "dingtalk-stream";
|
|
8
8
|
import { WEB_CONFIG_PORT, getPublicWebDashboardUrl } from "./constants.js";
|
|
9
|
-
import { CONFIG_PATH, getClaudeConfigHome, loadClaudeSettingsEnv, saveClaudeSettingsEnv, loadConfig, loadFileConfig, saveFileConfig } from "./config.js";
|
|
9
|
+
import { CONFIG_PATH, getClaudeConfigHome, loadClaudeSettingsEnv, saveClaudeSettingsEnv, loadConfig, loadFileConfig, saveFileConfig, CODEX_AUTH_PATHS } from "./config.js";
|
|
10
10
|
import { getWebDistDir, tryServeDashboardStatic } from "./config-web-static.js";
|
|
11
11
|
import { getServiceStatus, startBackgroundService, stopBackgroundService } from "./service-control.js";
|
|
12
12
|
import { initWeWork, stopWeWork } from "./wework/client.js";
|
|
@@ -333,6 +333,22 @@ function buildInitialPayload(file) {
|
|
|
333
333
|
codexCliPath: file.tools?.codex?.cliPath ?? "codex",
|
|
334
334
|
codebuddyCliPath: file.tools?.codebuddy?.cliPath ?? "codebuddy",
|
|
335
335
|
codexProxy: file.tools?.codex?.proxy ?? "",
|
|
336
|
+
codexApiKey: (() => {
|
|
337
|
+
if (process.env.OPENAI_API_KEY)
|
|
338
|
+
return maskSecret(process.env.OPENAI_API_KEY);
|
|
339
|
+
for (const p of CODEX_AUTH_PATHS) {
|
|
340
|
+
try {
|
|
341
|
+
if (existsSync(p)) {
|
|
342
|
+
const raw = JSON.parse(readFileSync(p, "utf-8"));
|
|
343
|
+
const key = raw?.openai_api_key ?? raw?.apiKey;
|
|
344
|
+
if (typeof key === "string" && key)
|
|
345
|
+
return maskSecret(key);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
catch { /* ignore */ }
|
|
349
|
+
}
|
|
350
|
+
return "";
|
|
351
|
+
})(),
|
|
336
352
|
logDir: file.logDir ?? "",
|
|
337
353
|
logLevel: file.logLevel ?? "default",
|
|
338
354
|
},
|
|
@@ -615,8 +631,9 @@ function toFileConfig(payload, existing) {
|
|
|
615
631
|
saveClaudeSettingsEnv(claudeEnv);
|
|
616
632
|
}
|
|
617
633
|
// claudeConfigPath is informational only, not saved
|
|
634
|
+
const { env: _discardLegacyRootEnv, ...existingWithoutRootEnv } = existing;
|
|
618
635
|
return {
|
|
619
|
-
...
|
|
636
|
+
...existingWithoutRootEnv,
|
|
620
637
|
aiCommand: payload.ai.aiCommand,
|
|
621
638
|
logDir: payload.ai.logDir === undefined ? existing.logDir : clean(payload.ai.logDir),
|
|
622
639
|
logLevel: payload.ai.logLevel === "default" ? undefined : payload.ai.logLevel,
|
|
@@ -883,6 +900,15 @@ export async function startWebConfigServer(options) {
|
|
|
883
900
|
return;
|
|
884
901
|
}
|
|
885
902
|
saveFileConfig(toFileConfig(body, loadFileConfig()));
|
|
903
|
+
// Save Codex OPENAI_API_KEY to ~/.codex/auth.json if provided
|
|
904
|
+
const codexApiKey = clean(body.ai.codexApiKey);
|
|
905
|
+
if (codexApiKey && !isMasked(codexApiKey)) {
|
|
906
|
+
const codexAuthPath = CODEX_AUTH_PATHS[0];
|
|
907
|
+
const codexDir = dirname(codexAuthPath);
|
|
908
|
+
if (!existsSync(codexDir))
|
|
909
|
+
mkdirSync(codexDir, { recursive: true });
|
|
910
|
+
writeFileSync(codexAuthPath, JSON.stringify({ openai_api_key: codexApiKey }, null, 2), "utf-8");
|
|
911
|
+
}
|
|
886
912
|
loadConfig();
|
|
887
913
|
json(response, 200, { message: "Configuration saved." }, request);
|
|
888
914
|
if (!options.persistent && requestUrl.searchParams.get("final") === "1") {
|
|
@@ -949,6 +975,56 @@ export async function startWebConfigServer(options) {
|
|
|
949
975
|
}
|
|
950
976
|
return;
|
|
951
977
|
}
|
|
978
|
+
// --- Codex settings (auth.json) ---
|
|
979
|
+
if (request.method === "GET" && requestUrl.pathname === "/api/codex/settings") {
|
|
980
|
+
try {
|
|
981
|
+
let foundPath = "";
|
|
982
|
+
let contents = "{}";
|
|
983
|
+
for (const p of CODEX_AUTH_PATHS) {
|
|
984
|
+
if (existsSync(p)) {
|
|
985
|
+
foundPath = p;
|
|
986
|
+
contents = readFileSync(p, "utf-8");
|
|
987
|
+
break;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
if (!foundPath && CODEX_AUTH_PATHS.length > 0) {
|
|
991
|
+
foundPath = CODEX_AUTH_PATHS[0];
|
|
992
|
+
}
|
|
993
|
+
json(response, 200, { path: foundPath, contents }, request);
|
|
994
|
+
}
|
|
995
|
+
catch (error) {
|
|
996
|
+
json(response, 500, { error: error instanceof Error ? error.message : String(error) }, request);
|
|
997
|
+
}
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
if (request.method === "POST" && requestUrl.pathname === "/api/codex/settings") {
|
|
1001
|
+
try {
|
|
1002
|
+
const body = await readJson(request);
|
|
1003
|
+
const raw = body.contents ?? "";
|
|
1004
|
+
if (!raw.trim()) {
|
|
1005
|
+
json(response, 400, { error: "contents is required" }, request);
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
try {
|
|
1009
|
+
JSON.parse(raw);
|
|
1010
|
+
}
|
|
1011
|
+
catch (err) {
|
|
1012
|
+
json(response, 400, { error: `Invalid JSON: ${err instanceof Error ? err.message : String(err)}` }, request);
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
const settingsPath = CODEX_AUTH_PATHS[0];
|
|
1016
|
+
const dir = dirname(settingsPath);
|
|
1017
|
+
if (!existsSync(dir)) {
|
|
1018
|
+
mkdirSync(dir, { recursive: true });
|
|
1019
|
+
}
|
|
1020
|
+
writeFileSync(settingsPath, raw, "utf-8");
|
|
1021
|
+
json(response, 200, { message: "Codex settings saved.", path: settingsPath }, request);
|
|
1022
|
+
}
|
|
1023
|
+
catch (error) {
|
|
1024
|
+
json(response, 500, { error: error instanceof Error ? error.message : String(error) }, request);
|
|
1025
|
+
}
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
952
1028
|
if (request.method === "GET" && requestUrl.pathname === "/api/health") {
|
|
953
1029
|
const file = loadFileConfig();
|
|
954
1030
|
const platforms = getHealthPlatformSnapshot(file);
|
package/dist/config.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type { Platform, AiCommand, Config, FilePlatformTelegram, FilePlatformFeishu, FilePlatformQQ, FilePlatformWechat, FilePlatformWework, FilePlatformDingtalk, FilePlatformWorkBuddy, FileToolClaude, FileToolCodex, FileToolCodeBuddy, FileConfig, } from './config/types.js';
|
|
2
2
|
import type { Platform, AiCommand, Config } from './config/types.js';
|
|
3
|
-
export { CONFIG_PATH, loadFileConfig, saveFileConfig, getClaudeConfigHome, loadClaudeSettingsEnv, saveClaudeSettingsEnv, normalizeAiCommand, hasCodexAuth, parseCommaSeparated, CLAUDE_AUTH_ENV_KEYS, refreshClaudeEnvToProcess, } from './config/file-io.js';
|
|
3
|
+
export { CONFIG_PATH, loadFileConfig, saveFileConfig, getClaudeConfigHome, loadClaudeSettingsEnv, saveClaudeSettingsEnv, normalizeAiCommand, hasCodexAuth, parseCommaSeparated, CLAUDE_AUTH_ENV_KEYS, refreshClaudeEnvToProcess, processEnvForNonClaudeCliChild, CODEX_AUTH_PATHS, } from './config/file-io.js';
|
|
4
4
|
/** 检测是否需要交互式配置(无 token 且无环境变量) */
|
|
5
5
|
export declare function needsSetup(): boolean;
|
|
6
6
|
export declare function loadConfig(): Config;
|
package/dist/config.js
CHANGED
|
@@ -11,7 +11,7 @@ import { createLogger } from './logger.js';
|
|
|
11
11
|
import { APP_HOME, DEFAULT_TELEMETRY_INGEST_URL, DEFAULT_TELEMETRY_INGEST_TOKEN, } from './constants.js';
|
|
12
12
|
const log = createLogger('config');
|
|
13
13
|
// Re-export file I/O and credential helpers from sub-modules
|
|
14
|
-
export { CONFIG_PATH, loadFileConfig, saveFileConfig, getClaudeConfigHome, loadClaudeSettingsEnv, saveClaudeSettingsEnv, normalizeAiCommand, hasCodexAuth, parseCommaSeparated, CLAUDE_AUTH_ENV_KEYS, refreshClaudeEnvToProcess, } from './config/file-io.js';
|
|
14
|
+
export { CONFIG_PATH, loadFileConfig, saveFileConfig, getClaudeConfigHome, loadClaudeSettingsEnv, saveClaudeSettingsEnv, normalizeAiCommand, hasCodexAuth, parseCommaSeparated, CLAUDE_AUTH_ENV_KEYS, refreshClaudeEnvToProcess, processEnvForNonClaudeCliChild, CODEX_AUTH_PATHS, } from './config/file-io.js';
|
|
15
15
|
import { loadFileConfig, normalizeAiCommand, hasCodexAuth, parseCommaSeparated, loadClaudeSettingsEnv, } from './config/file-io.js';
|
|
16
16
|
/** 检测是否需要交互式配置(无 token 且无环境变量) */
|
|
17
17
|
export function needsSetup() {
|
|
@@ -50,20 +50,8 @@ export function needsSetup() {
|
|
|
50
50
|
}
|
|
51
51
|
export function loadConfig() {
|
|
52
52
|
const file = loadFileConfig();
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
for (const [key, value] of Object.entries(env)) {
|
|
56
|
-
if (!(key in process.env) && value != null && typeof key === 'string') {
|
|
57
|
-
process.env[key] = String(value);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
// 1. 全局 env(最低优先级之一)
|
|
62
|
-
if (file.env)
|
|
63
|
-
mergeEnv(file.env);
|
|
64
|
-
// 2. tools.claude.env 和 Claude Code 的 ~/.claude/settings.json 不在此处合并到 process.env,
|
|
65
|
-
// 改由 Claude adapter 在创建 session 前按需加载(refreshClaudeEnvToProcess),
|
|
66
|
-
// 避免 Claude 凭证泄漏到 Codex / CodeBuddy 等子进程。
|
|
53
|
+
// tools.claude.env 与 ~/.claude/settings.json 不在此处合并到 process.env,
|
|
54
|
+
// 由 Claude adapter 在创建 session 前 refreshClaudeEnvToProcess,避免污染 Codex/CodeBuddy 子进程。
|
|
67
55
|
const fileTelegram = file.platforms?.telegram;
|
|
68
56
|
const fileFeishu = file.platforms?.feishu;
|
|
69
57
|
const fileQQ = file.platforms?.qq;
|
|
@@ -309,7 +297,7 @@ export function loadConfig() {
|
|
|
309
297
|
}
|
|
310
298
|
if (!hasCodexAuth()) {
|
|
311
299
|
log.warn('Codex 模式:未检测到 OPENAI_API_KEY 或 Codex 登录态。首次使用请先运行 codex login,' +
|
|
312
|
-
'或在
|
|
300
|
+
'或在 shell 中 export OPENAI_API_KEY。');
|
|
313
301
|
}
|
|
314
302
|
}
|
|
315
303
|
// 8. 校验 CodeBuddy CLI(使用 codebuddy 时)
|
package/dist/setup.js
CHANGED
|
@@ -709,23 +709,25 @@ export async function runInteractiveSetup() {
|
|
|
709
709
|
// 如果选择 Claude,询问 API 配置
|
|
710
710
|
let claudeApiConfig = {};
|
|
711
711
|
if (commonResp.aiCommand === "claude") {
|
|
712
|
-
|
|
712
|
+
const legacyRootEnv = existing?.env;
|
|
713
|
+
const claudeFileEnv = existing?.tools?.claude?.env ?? legacyRootEnv;
|
|
714
|
+
// 检查是否已配置 API 密钥(环境变量、tools.claude.env、旧版根 env、或 ~/.claude/settings.json)
|
|
713
715
|
const hasExistingApiKey = !!(process.env.ANTHROPIC_API_KEY ||
|
|
714
716
|
process.env.ANTHROPIC_AUTH_TOKEN ||
|
|
715
717
|
process.env.CLAUDE_CODE_OAUTH_TOKEN ||
|
|
716
|
-
|
|
717
|
-
|
|
718
|
+
claudeFileEnv?.ANTHROPIC_API_KEY ||
|
|
719
|
+
claudeFileEnv?.ANTHROPIC_AUTH_TOKEN ||
|
|
718
720
|
hasClaudeCredsInSettings());
|
|
719
721
|
if (hasExistingApiKey) {
|
|
720
722
|
// 已经配置过,直接保留原有配置,跳过询问
|
|
721
|
-
if (
|
|
723
|
+
if (claudeFileEnv && Object.keys(claudeFileEnv).length > 0) {
|
|
722
724
|
claudeApiConfig = {
|
|
723
|
-
apiKey:
|
|
724
|
-
baseUrl:
|
|
725
|
-
model:
|
|
726
|
-
haikuModel:
|
|
727
|
-
sonnetModel:
|
|
728
|
-
opusModel:
|
|
725
|
+
apiKey: claudeFileEnv.ANTHROPIC_API_KEY || claudeFileEnv.ANTHROPIC_AUTH_TOKEN,
|
|
726
|
+
baseUrl: claudeFileEnv.ANTHROPIC_BASE_URL,
|
|
727
|
+
model: claudeFileEnv.ANTHROPIC_MODEL,
|
|
728
|
+
haikuModel: claudeFileEnv.ANTHROPIC_DEFAULT_HAIKU_MODEL,
|
|
729
|
+
sonnetModel: claudeFileEnv.ANTHROPIC_DEFAULT_SONNET_MODEL,
|
|
730
|
+
opusModel: claudeFileEnv.ANTHROPIC_DEFAULT_OPUS_MODEL,
|
|
729
731
|
};
|
|
730
732
|
}
|
|
731
733
|
}
|
|
@@ -805,18 +807,7 @@ export async function runInteractiveSetup() {
|
|
|
805
807
|
const base = existing
|
|
806
808
|
? JSON.parse(JSON.stringify(existing))
|
|
807
809
|
: null;
|
|
808
|
-
const { telegramBotToken: _, feishuAppId: __, feishuAppSecret: ___, claudeWorkDir: _cwd, claudeTimeoutMs: _ctm, claudeModel: _cm, ...baseRest } = (base ?? {});
|
|
809
|
-
// Claude API 凭证不存入 config.json,仅从 ~/.claude/settings.json 或环境变量读取
|
|
810
|
-
const ANTHROPIC_KEYS = [
|
|
811
|
-
"ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_BASE_URL",
|
|
812
|
-
"ANTHROPIC_MODEL", "ANTHROPIC_DEFAULT_HAIKU_MODEL", "ANTHROPIC_DEFAULT_SONNET_MODEL", "ANTHROPIC_DEFAULT_OPUS_MODEL",
|
|
813
|
-
];
|
|
814
|
-
const envConfig = {};
|
|
815
|
-
for (const [k, v] of Object.entries(base?.env ?? {})) {
|
|
816
|
-
if (v != null && typeof v === "string" && !ANTHROPIC_KEYS.includes(k)) {
|
|
817
|
-
envConfig[k] = v;
|
|
818
|
-
}
|
|
819
|
-
}
|
|
810
|
+
const { telegramBotToken: _, feishuAppId: __, feishuAppSecret: ___, claudeWorkDir: _cwd, claudeTimeoutMs: _ctm, claudeModel: _cm, env: _stripLegacyRootEnv, ...baseRest } = (base ?? {});
|
|
820
811
|
// 若用户在向导中输入了 Claude 配置,写入 ~/.claude/settings.json(与 Claude Code 共用)
|
|
821
812
|
if (claudeApiConfig.apiKey || claudeApiConfig.baseUrl || claudeApiConfig.model) {
|
|
822
813
|
const claudeExisting = loadClaudeSettings();
|
|
@@ -854,7 +845,6 @@ export async function runInteractiveSetup() {
|
|
|
854
845
|
const out = {
|
|
855
846
|
...baseRest,
|
|
856
847
|
platforms: { ...(base?.platforms ?? {}) },
|
|
857
|
-
env: Object.keys(envConfig).length > 0 ? envConfig : undefined,
|
|
858
848
|
aiCommand: aiCmd,
|
|
859
849
|
tools: {
|
|
860
850
|
claude: {
|