codexmate 0.0.23 → 0.0.25

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 (73) hide show
  1. package/README.md +32 -9
  2. package/README.zh.md +33 -9
  3. package/cli/auth-profiles.js +23 -7
  4. package/cli/builtin-proxy.js +35 -0
  5. package/cli/claude-proxy.js +24 -0
  6. package/cli/doctor-core.js +903 -0
  7. package/cli/import-skills-url.js +356 -0
  8. package/cli/openai-bridge.js +51 -4
  9. package/cli/session-usage.js +8 -2
  10. package/cli.js +1921 -399
  11. package/lib/automation.js +404 -0
  12. package/lib/cli-models-utils.js +0 -40
  13. package/lib/cli-network-utils.js +28 -2
  14. package/lib/cli-path-utils.js +21 -5
  15. package/lib/cli-sessions.js +32 -1
  16. package/lib/download-artifacts.js +17 -2
  17. package/lib/mcp-stdio.js +13 -0
  18. package/package.json +3 -3
  19. package/plugins/README.md +20 -0
  20. package/plugins/README.zh-CN.md +20 -0
  21. package/plugins/prompt-templates/comment-polish/index.mjs +25 -0
  22. package/plugins/prompt-templates/computed.mjs +253 -0
  23. package/plugins/prompt-templates/index.mjs +8 -0
  24. package/plugins/prompt-templates/manifest.mjs +15 -0
  25. package/plugins/prompt-templates/methods.mjs +619 -0
  26. package/plugins/prompt-templates/overview.mjs +90 -0
  27. package/plugins/prompt-templates/ownership.mjs +19 -0
  28. package/plugins/prompt-templates/rule-ack/index.mjs +21 -0
  29. package/plugins/prompt-templates/storage.mjs +64 -0
  30. package/plugins/registry.mjs +16 -0
  31. package/web-ui/app.js +21 -35
  32. package/web-ui/index.html +4 -3
  33. package/web-ui/logic.sessions.mjs +2 -2
  34. package/web-ui/modules/app.computed.dashboard.mjs +24 -22
  35. package/web-ui/modules/app.computed.main-tabs.mjs +3 -0
  36. package/web-ui/modules/app.computed.session.mjs +17 -0
  37. package/web-ui/modules/app.methods.agents.mjs +91 -3
  38. package/web-ui/modules/app.methods.codex-config.mjs +153 -164
  39. package/web-ui/modules/app.methods.install.mjs +28 -0
  40. package/web-ui/modules/app.methods.navigation.mjs +34 -1
  41. package/web-ui/modules/app.methods.runtime.mjs +24 -2
  42. package/web-ui/modules/app.methods.session-actions.mjs +8 -1
  43. package/web-ui/modules/app.methods.session-browser.mjs +37 -6
  44. package/web-ui/modules/app.methods.session-trash.mjs +4 -2
  45. package/web-ui/modules/config-mode.computed.mjs +1 -3
  46. package/web-ui/modules/i18n.dict.mjs +2055 -0
  47. package/web-ui/modules/i18n.mjs +2 -1769
  48. package/web-ui/partials/index/layout-header.html +48 -34
  49. package/web-ui/partials/index/modal-config-template-agents.html +3 -4
  50. package/web-ui/partials/index/modal-health-check.html +33 -60
  51. package/web-ui/partials/index/panel-config-claude.html +35 -15
  52. package/web-ui/partials/index/panel-config-codex.html +47 -19
  53. package/web-ui/partials/index/panel-config-openclaw.html +8 -3
  54. package/web-ui/partials/index/panel-dashboard.html +186 -0
  55. package/web-ui/partials/index/panel-docs.html +1 -1
  56. package/web-ui/partials/index/panel-market.html +3 -0
  57. package/web-ui/partials/index/panel-orchestration.html +3 -0
  58. package/web-ui/partials/index/panel-plugins.html +16 -10
  59. package/web-ui/partials/index/panel-sessions.html +8 -3
  60. package/web-ui/partials/index/panel-settings.html +1 -1
  61. package/web-ui/partials/index/panel-usage.html +9 -1
  62. package/web-ui/res/logo-pack.webp +0 -0
  63. package/web-ui/styles/controls-forms.css +58 -4
  64. package/web-ui/styles/dashboard.css +274 -0
  65. package/web-ui/styles/layout-shell.css +3 -2
  66. package/web-ui/styles/responsive.css +0 -2
  67. package/web-ui/styles/sessions-list.css +5 -7
  68. package/web-ui/styles/sessions-toolbar-trash.css +4 -4
  69. package/web-ui/styles/sessions-usage.css +33 -0
  70. package/web-ui/styles.css +1 -0
  71. package/res/logo.png +0 -0
  72. /package/{res → web-ui/res}/json5.min.js +0 -0
  73. /package/{res → web-ui/res}/vue.global.prod.js +0 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  <div align="center">
2
2
 
3
- <img src="res/logo.png" alt="Codex Mate logo" width="180" />
3
+ <img src="site/.vitepress/public/images/logo.png" alt="Codex Mate logo" width="180" />
4
4
 
5
5
  # Codex Mate
6
6
 
@@ -29,7 +29,7 @@ Codex Mate is a local-first CLI + Web UI for unified management of:
29
29
  - Claude Code `CLAUDE.md` editing (writes to `~/.claude/CLAUDE.md`)
30
30
  - OpenClaw JSON5 profiles and workspace `AGENTS.md`
31
31
  - Local skills market for Codex / Claude Code (target switching, local skills management, cross-app import, ZIP distribution)
32
- - Local Codex/Claude sessions (list/filter/export/delete) with Usage analytics overview
32
+ - Local Codex/Claude/Gemini CLI/CodeBuddy Code sessions (list/filter/export/delete) with Usage analytics overview
33
33
  - Plugins (Prompt templates): reusable templates with variables and one-click copy
34
34
  - Task orchestration: plan/queue/run/review local tasks
35
35
 
@@ -57,12 +57,20 @@ It works on local files directly and does not require cloud hosting. The skills
57
57
  - OpenClaw JSON5 profile management
58
58
 
59
59
  **Session Management**
60
- - Unified Codex + Claude session list
60
+ - Unified Codex + Claude + Gemini CLI + CodeBuddy Code session list
61
+ - Session locations (local-first, configurable):
62
+ - Codex: `~/.codex/sessions/*.jsonl` (or `$CODEX_HOME/sessions`, `$XDG_CONFIG_HOME/codex/sessions`)
63
+ - Claude: `~/.claude/projects/**/**/*.jsonl` (or `$CLAUDE_HOME/projects`, `$XDG_CONFIG_HOME/claude/projects`)
64
+ - Gemini: `~/.gemini/tmp/*/chats/*.json` (or `$GEMINI_HOME/tmp`, `$XDG_CONFIG_HOME/gemini/tmp`)
65
+ - CodeBuddy: `~/.codebuddy/projects/**/**/*.jsonl` (or `$CODEBUDDY_CODE_HOME_DIR/projects`)
61
66
  - Local session pinning with persistent pinned state and pinned-first ordering
62
- - Keyword/source/cwd filters
67
+ - Keyword/source/cwd/role/time filters, plus shareable filter links
68
+ - Copy resume command (Codex/Gemini/CodeBuddy): `codex resume <sessionId>` / `gemini -r <sessionId>` / `codebuddy -r <sessionId>`
69
+ - Fast search UX: short-lived query result caching to avoid rescanning on each keystroke
63
70
  - Usage subview with 7d / 30d session trends, message trends, source share, and top paths
64
- - Markdown export
65
- - Session-level and message-level delete (supports batch)
71
+ - Markdown export (Web UI + `codexmate export-session`, supports `--session-id` or `--file`)
72
+ - Session-level and message-level delete (supports batch), with a local recycle bin for restore/purge
73
+ - Large-session preview optimization (fast tail preview path)
66
74
 
67
75
  **Skills Market**
68
76
  - Switch the skills install target between Codex and Claude Code
@@ -76,10 +84,20 @@ It works on local files directly and does not require cloud hosting. The skills
76
84
 
77
85
  **Engineering Utilities**
78
86
  - MCP stdio domains (`tools`, `resources`, `prompts`)
87
+ - Automation hooks (`/hooks/*`) + outbound webhook notifiers
79
88
  - Built-in proxy controls (`proxy`)
80
89
  - Auth profile management (`auth`)
81
90
  - Zip/unzip utilities
82
91
 
92
+ ## Automation (signal → action)
93
+
94
+ When running `codexmate run`, you can accept external webhooks and convert them into queued tasks:
95
+
96
+ - Webhook entry: `POST /hooks/<source>` (currently `github`, `gitlab`)
97
+ - Rule config: `~/.codex/codexmate-automation.json`
98
+ - Supported action: `task.queue.add` (optionally `startQueue: true`)
99
+ - Notifications: `notifiers[]` supports `type: "webhook"` for Slack/Feishu-style incoming webhooks
100
+
83
101
  ## Architecture
84
102
 
85
103
  ### At a glance (what it does → what you get)
@@ -164,6 +182,12 @@ npm install -g @mmmbuto/codex-cli-termux@latest
164
182
 
165
183
  # Claude Code
166
184
  npm install -g @anthropic-ai/claude-code
185
+
186
+ # Gemini CLI
187
+ npm install -g @google/gemini-cli
188
+
189
+ # CodeBuddy Code
190
+ npm install -g @tencent-ai/codebuddy-code
167
191
  ```
168
192
 
169
193
  ### Run from source
@@ -190,7 +214,7 @@ npm run reset
190
214
  npm run reset 79
191
215
  ```
192
216
 
193
- - `npm run reset`: prompt for a PR number; leave it blank to return to default `origin/main`
217
+ - `npm run reset`: reset to default `origin/main`
194
218
  - `npm run reset 79`: sync directly to the latest head snapshot of PR `#79`
195
219
  - The script also handles local branch switching, workspace cleanup, untracked file cleanup, and final state validation
196
220
 
@@ -206,13 +230,12 @@ npm run reset 79
206
230
  | `codexmate delete <name>` | Delete provider |
207
231
  | `codexmate claude <BaseURL> <API_KEY> [model]` | Write Claude Code config |
208
232
  | `codexmate auth <list\|import\|switch\|delete\|status>` | Auth profile management |
209
- | `codexmate proxy <status\|set\|apply\|enable\|start\|stop>` | Built-in proxy management |
210
233
  | `codexmate workflow <list\|get\|validate\|run\|runs>` | MCP workflow management |
211
234
  | `codexmate codex [args...] [--follow-up <text> repeatable]` | Codex CLI passthrough entrypoint (auto-adds `--yolo`, supports queued follow-up appends) |
212
235
  | `codexmate qwen [args...]` | Qwen CLI passthrough entrypoint |
213
236
  | `codexmate run [--host <HOST>] [--no-browser]` | Start Web UI |
214
237
  | `codexmate mcp serve [--read-only\|--allow-write]` | Start MCP stdio server |
215
- | `codexmate export-session --source <codex\|claude> ...` | Export session to Markdown |
238
+ | `codexmate export-session --source <codex\|claude\|gemini\|codebuddy> ...` | Export session to Markdown |
216
239
  | `codexmate zip <path> [--max:0-9]` / `codexmate unzip <zip> [out]` | Zip / unzip |
217
240
  | `codexmate unzip-ext <zip-dir> [out] [--ext:suffix[,suffix...]] [--no-recursive]` | Extract files with target suffixes from ZIP files in a directory (default `.json`, recursive by default) |
218
241
 
package/README.zh.md CHANGED
@@ -1,6 +1,6 @@
1
1
  <div align="center">
2
2
 
3
- <img src="res/logo.png" alt="Codex Mate logo" width="180" />
3
+ <img src="site/.vitepress/public/images/logo.png" alt="Codex Mate logo" width="180" />
4
4
 
5
5
  # Codex Mate
6
6
 
@@ -29,7 +29,7 @@ Codex Mate 提供一套本地优先的 CLI + Web UI,用于统一管理:
29
29
  - Claude Code `CLAUDE.md` 编辑(写入 `~/.claude/CLAUDE.md`)
30
30
  - OpenClaw JSON5 配置与 Workspace `AGENTS.md`
31
31
  - Codex / Claude Code Skills 市场(安装目标切换、本地 skills 管理、跨应用导入、ZIP 分发)
32
- - Codex / Claude 本地会话浏览、筛选、导出、删除与 Usage 统计概览
32
+ - Codex / Claude / Gemini CLI / CodeBuddy Code 本地会话浏览、筛选、导出、删除与 Usage 统计概览
33
33
  - 插件(提示词模板):模板复用、变量填写、一键复制
34
34
  - 任务编排:规划 / 排队 / 执行 / 回看
35
35
 
@@ -58,12 +58,20 @@ Codex Mate 提供一套本地优先的 CLI + Web UI,用于统一管理:
58
58
  - OpenClaw JSON5 配置方案管理
59
59
 
60
60
  **会话管理**
61
- - 同页查看 Codex 与 Claude 会话
61
+ - 同页查看 Codex、Claude、Gemini CLI CodeBuddy Code 会话
62
+ - 会话来源与默认路径(本地优先,可通过环境变量覆盖):
63
+ - Codex:`~/.codex/sessions/*.jsonl`(或 `$CODEX_HOME/sessions`、`$XDG_CONFIG_HOME/codex/sessions`)
64
+ - Claude:`~/.claude/projects/**/**/*.jsonl`(或 `$CLAUDE_HOME/projects`、`$XDG_CONFIG_HOME/claude/projects`)
65
+ - Gemini:`~/.gemini/tmp/*/chats/*.json`(或 `$GEMINI_HOME/tmp`、`$XDG_CONFIG_HOME/gemini/tmp`)
66
+ - CodeBuddy:`~/.codebuddy/projects/**/**/*.jsonl`(或 `$CODEBUDDY_CODE_HOME_DIR/projects`)
62
67
  - 支持本地会话置顶,置顶状态持久化保存并优先排序显示
63
- - 关键词搜索、来源筛选、cwd 路径筛选
68
+ - 关键词搜索、来源筛选、cwd/角色/时间筛选,并支持复制筛选链接
69
+ - 复制恢复命令(Codex/Gemini/CodeBuddy):`codex resume <sessionId>` / `gemini -r <sessionId>` / `codebuddy -r <sessionId>`
70
+ - 搜索体验优化:短周期结果缓存,避免输入时重复扫描
64
71
  - Usage 子页:近 7 天 / 近 30 天会话趋势、消息趋势、来源占比、高频路径
65
- - 会话导出 Markdown
66
- - 会话与消息级删除(支持批量)
72
+ - 会话导出 Markdown(Web UI + `codexmate export-session`,支持 `--session-id` 或 `--file`)
73
+ - 会话与消息级删除(支持批量),并提供本地回收站用于恢复/彻底删除
74
+ - 大会话预览优化(快速 tail 预览路径)
67
75
 
68
76
  **Skills 市场**
69
77
  - 在 Codex 与 Claude Code 之间切换 skills 安装目标
@@ -81,8 +89,18 @@ Codex Mate 提供一套本地优先的 CLI + Web UI,用于统一管理:
81
89
 
82
90
  **工程能力**
83
91
  - MCP stdio 能力(tools/resources/prompts)
92
+ - 自动化钩子(`/hooks/*`)+ 外发 webhook 通知
84
93
  - Zip 压缩/解压(优先系统工具,失败回退 JS 库)
85
94
 
95
+ ## 自动化(信号 → 行动)
96
+
97
+ 运行 `codexmate run` 后,可接收外部 webhook 并转为任务队列:
98
+
99
+ - 入口:`POST /hooks/<source>`(当前支持 `github`、`gitlab`)
100
+ - 规则:`~/.codex/codexmate-automation.json`
101
+ - 动作:`task.queue.add`(可选 `startQueue: true`)
102
+ - 通知:`notifiers[]` 支持 `type: "webhook"`(适配 Slack/飞书等入站 webhook)
103
+
86
104
  ## 架构总览
87
105
 
88
106
  ### 一图看懂(从“做什么”到“产生什么效果”)
@@ -154,7 +172,7 @@ codexmate run
154
172
 
155
173
  > 安全提示:默认监听会在当前局域网暴露未鉴权的管理界面。若包含 API Key、provider 配置或 skills 管理,请仅在可信网络中使用;如需仅本机访问,可设置 `CODEXMATE_HOST=127.0.0.1` 或启动时传入 `--host 127.0.0.1`。
156
174
 
157
- ### 安装 Codex CLI / Claude Code(可选)
175
+ ### 安装 Codex CLI / Claude Code / Gemini CLI / CodeBuddy Code(可选)
158
176
 
159
177
  Codex Mate 支持透传调用官方 CLI(例如 `codexmate codex ...`),建议先安装:
160
178
 
@@ -167,6 +185,12 @@ npm install -g @mmmbuto/codex-cli-termux@latest
167
185
 
168
186
  # Claude Code
169
187
  npm install -g @anthropic-ai/claude-code
188
+
189
+ # Gemini CLI
190
+ npm install -g @google/gemini-cli
191
+
192
+ # CodeBuddy Code
193
+ npm install -g @tencent-ai/codebuddy-code
170
194
  ```
171
195
 
172
196
  ### 从源码运行
@@ -193,7 +217,7 @@ npm run reset
193
217
  npm run reset 79
194
218
  ```
195
219
 
196
- - `npm run reset`:交互输入 PR 编号;留空则回到默认 `origin/main`
220
+ - `npm run reset`:直接重置到默认 `origin/main`
197
221
  - `npm run reset 79`:直接同步到 PR `#79` 的最新 head 快照
198
222
  - 脚本会自动完成本地分支切换、工作区清理、未跟踪文件清理与最终状态校验
199
223
 
@@ -213,7 +237,7 @@ npm run reset 79
213
237
  | `codexmate qwen [args...]` | Qwen CLI 透传入口 |
214
238
  | `codexmate run [--host <HOST>] [--no-browser]` | 启动 Web UI |
215
239
  | `codexmate mcp serve [--read-only\|--allow-write]` | 启动 MCP stdio 服务 |
216
- | `codexmate export-session --source <codex\|claude> ...` | 导出会话为 Markdown |
240
+ | `codexmate export-session --source <codex\|claude\|gemini\|codebuddy> ...` | 导出会话为 Markdown |
217
241
  | `codexmate zip <path> [--max:0-9]` / `codexmate unzip <zip> [out]` | 压缩 / 解压 |
218
242
  | `codexmate unzip-ext <zip-dir> [out] [--ext:suffix[,suffix...]] [--no-recursive]` | 批量提取目录下 ZIP 内指定后缀文件(默认 `.json`,默认递归) |
219
243
 
@@ -28,12 +28,17 @@ function createAuthProfileController(deps = {}) {
28
28
  function normalizeAuthProfileName(value) {
29
29
  const raw = typeof value === 'string' ? value.trim() : '';
30
30
  if (!raw) return '';
31
- const sanitized = raw
31
+ return raw.slice(0, 120);
32
+ }
33
+
34
+ function sanitizeAuthProfileFileStem(value) {
35
+ const raw = typeof value === 'string' ? value.trim() : '';
36
+ if (!raw) return '';
37
+ return raw
32
38
  .replace(/[\\/:*?"<>|]/g, '-')
33
39
  .replace(/\s+/g, '-')
34
40
  .replace(/^-+|-+$/g, '')
35
41
  .slice(0, 120);
36
- return sanitized;
37
42
  }
38
43
 
39
44
  function normalizeAuthRegistry(raw) {
@@ -46,7 +51,7 @@ function createAuthProfileController(deps = {}) {
46
51
  version: 1,
47
52
  current: typeof raw.current === 'string' ? raw.current.trim() : '',
48
53
  items: items.map((item) => ({
49
- name: normalizeAuthProfileName(item.name) || item.name.trim(),
54
+ name: item.name.trim(),
50
55
  fileName: typeof item.fileName === 'string' ? path.basename(item.fileName) : '',
51
56
  type: typeof item.type === 'string' ? item.type : '',
52
57
  email: typeof item.email === 'string' ? item.email : '',
@@ -135,13 +140,24 @@ function createAuthProfileController(deps = {}) {
135
140
  const sourceFile = typeof options.sourceFile === 'string' ? options.sourceFile : '';
136
141
  const preferredName = normalizeAuthProfileName(options.name || '');
137
142
  const profileName = preferredName || getAuthProfileNameFallback(safePayload, sourceFile);
138
- const fileName = `${profileName}.json`;
139
- const profilePath = path.join(AUTH_PROFILES_DIR, fileName);
143
+ let fileStem = sanitizeAuthProfileFileStem(profileName) || sanitizeAuthProfileFileStem(sourceFile) || `auth-${Date.now()}`;
144
+
145
+ const registry = readAuthRegistry();
146
+ const existed = registry.items.find((item) => item && item.name === profileName);
147
+ if (existed && existed.fileName) {
148
+ fileStem = path.basename(existed.fileName, path.extname(existed.fileName));
149
+ }
150
+
151
+ let fileName = `${fileStem}.json`;
152
+ let profilePath = path.join(AUTH_PROFILES_DIR, fileName);
153
+ if (!existed && fs.existsSync(profilePath)) {
154
+ fileStem = `${fileStem}-${Date.now().toString(16).slice(-6)}`;
155
+ fileName = `${fileStem}.json`;
156
+ profilePath = path.join(AUTH_PROFILES_DIR, fileName);
157
+ }
140
158
 
141
159
  ensureDir(AUTH_PROFILES_DIR);
142
160
  writeJsonAtomic(profilePath, safePayload);
143
-
144
- const registry = readAuthRegistry();
145
161
  const meta = buildAuthProfileSummary(profileName, safePayload, fileName);
146
162
  meta.importedAt = toIsoTime(Date.now());
147
163
  meta.sourceFile = sourceFile || '';
@@ -674,6 +674,41 @@ function createBuiltinProxyRuntimeController(deps = {}) {
674
674
  return;
675
675
  }
676
676
 
677
+ const remoteAddr = req && req.socket ? req.socket.remoteAddress : '';
678
+ const isLoopback = !remoteAddr
679
+ || remoteAddr === '127.0.0.1'
680
+ || remoteAddr === '::1'
681
+ || remoteAddr === '::ffff:127.0.0.1';
682
+ if (!isLoopback) {
683
+ const expected = typeof process.env.CODEXMATE_HTTP_TOKEN === 'string'
684
+ ? process.env.CODEXMATE_HTTP_TOKEN.trim()
685
+ : '';
686
+ if (!expected) {
687
+ const body = JSON.stringify({ error: 'Remote access is disabled (set CODEXMATE_HTTP_TOKEN)' });
688
+ res.writeHead(403, {
689
+ 'Content-Type': 'application/json; charset=utf-8',
690
+ 'Content-Length': Buffer.byteLength(body, 'utf-8')
691
+ });
692
+ res.end(body, 'utf-8');
693
+ return;
694
+ }
695
+ const headers = req && req.headers && typeof req.headers === 'object' ? req.headers : {};
696
+ const rawAuth = typeof headers.authorization === 'string' ? headers.authorization.trim() : '';
697
+ const match = rawAuth ? rawAuth.match(/^bearer\s+(.+)$/i) : null;
698
+ const actual = match && match[1]
699
+ ? match[1].trim()
700
+ : (rawAuth ? rawAuth : (typeof headers['x-codexmate-token'] === 'string' ? String(headers['x-codexmate-token']).trim() : ''));
701
+ if (!actual || actual !== expected) {
702
+ const body = JSON.stringify({ error: 'Unauthorized' });
703
+ res.writeHead(401, {
704
+ 'Content-Type': 'application/json; charset=utf-8',
705
+ 'Content-Length': Buffer.byteLength(body, 'utf-8')
706
+ });
707
+ res.end(body, 'utf-8');
708
+ return;
709
+ }
710
+ }
711
+
677
712
  const incomingPath = parsedIncoming.pathname || '/';
678
713
  if (incomingPath === '/health' || incomingPath === '/status') {
679
714
  const body = JSON.stringify({
@@ -864,6 +864,30 @@ function createBuiltinClaudeProxyRuntimeController(deps = {}) {
864
864
  function createBuiltinClaudeProxyServer(settings, upstream) {
865
865
  const connections = new Set();
866
866
  const server = http.createServer((req, res) => {
867
+ const remoteAddr = req && req.socket ? req.socket.remoteAddress : '';
868
+ const isLoopback = !remoteAddr
869
+ || remoteAddr === '127.0.0.1'
870
+ || remoteAddr === '::1'
871
+ || remoteAddr === '::ffff:127.0.0.1';
872
+ if (!isLoopback) {
873
+ const expected = typeof process.env.CODEXMATE_HTTP_TOKEN === 'string'
874
+ ? process.env.CODEXMATE_HTTP_TOKEN.trim()
875
+ : '';
876
+ if (!expected) {
877
+ writeAnthropicProxyError(res, 403, 'Remote access is disabled (set CODEXMATE_HTTP_TOKEN)', 'authentication_error');
878
+ return;
879
+ }
880
+ const headers = req && req.headers && typeof req.headers === 'object' ? req.headers : {};
881
+ const rawAuth = typeof headers.authorization === 'string' ? headers.authorization.trim() : '';
882
+ const match = rawAuth ? rawAuth.match(/^bearer\s+(.+)$/i) : null;
883
+ const actual = match && match[1]
884
+ ? match[1].trim()
885
+ : (rawAuth ? rawAuth : (typeof headers['x-codexmate-token'] === 'string' ? String(headers['x-codexmate-token']).trim() : ''));
886
+ if (!actual || actual !== expected) {
887
+ writeAnthropicProxyError(res, 401, 'Unauthorized', 'authentication_error');
888
+ return;
889
+ }
890
+ }
867
891
  handleBuiltinClaudeProxyRequest(req, res, settings, upstream).catch((err) => {
868
892
  if (res.headersSent) {
869
893
  try { res.destroy(err); } catch (_) {}