Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
cc72616
feat(sessions): add workspace memory summary
awsl233777 Jun 24, 2026
584acba
fix(sessions): harden workspace summary regressions
awsl233777 Jun 25, 2026
28a508c
fix(claude): route local bridge by target api
awsl233777 Jun 25, 2026
ca36fa3
test(claude): cover batch health checks
awsl233777 Jun 25, 2026
92aab49
fix(claude): allow ollama health checks without api key
awsl233777 Jun 25, 2026
c4155aa
fix(sessions): address workspace review nits
awsl233777 Jun 25, 2026
7bbd79d
fix(claude): avoid forwarding bridge token upstream
awsl233777 Jun 25, 2026
9b274cd
fix(claude): validate local bridge request bodies
awsl233777 Jun 25, 2026
77c7dd1
test(health): cover codex batch speed checks
awsl233777 Jun 25, 2026
2cb5743
fix(health): skip local bridge batch speed tests
awsl233777 Jun 25, 2026
0fb8296
fix(health): route settings checks through codex config
awsl233777 Jun 26, 2026
53b715e
fix(health): preserve codex probe failure state
awsl233777 Jun 26, 2026
29dba3d
fix(health): clarify codex settings health check
awsl233777 Jun 26, 2026
c58c68c
fix(health): support failed provider cleanup
awsl233777 Jun 26, 2026
b59ddc8
fix(health): add failed provider bulk selection
awsl233777 Jun 26, 2026
b37a469
fix(health): refine failed provider modal layout
awsl233777 Jun 26, 2026
5a5cc13
fix(health): skip per-item confirms in bulk cleanup
awsl233777 Jun 26, 2026
2b2c1fc
docs(readme): describe provider health cleanup
awsl233777 Jun 26, 2026
bf7c1cf
fix(health): close modal after bulk cleanup
awsl233777 Jun 26, 2026
82191fb
fix(providers): prune deleted provider cache entries
awsl233777 Jun 26, 2026
fd9ecdb
fix(config): improve provider card title visibility
awsl233777 Jun 26, 2026
e6e85dc
test(config): fix provider cache refresh assertion
awsl233777 Jun 26, 2026
a94959b
fix(config): default new providers to numeric names
awsl233777 Jun 26, 2026
29f5748
fix(config): support provider cards on touch devices
awsl233777 Jun 26, 2026
68d65a5
fix(config): prune Claude provider cache on delete
awsl233777 Jun 26, 2026
8d1401d
fix(config): scope health checks and prune stale Claude cache
awsl233777 Jun 26, 2026
055606d
fix(config): handle Claude cache delete transport errors
awsl233777 Jun 26, 2026
a244faa
fix(claude): suppress deleted settings reimports
awsl233777 Jun 26, 2026
e77237c
fix(health): prune Claude speed-test cleanup state
awsl233777 Jun 26, 2026
6ee6668
fix(ui): keep health check checkboxes visible
awsl233777 Jun 26, 2026
9319146
chore(release): bump version to 0.0.56
awsl233777 Jun 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ Unlike simple wrappers, Codex Mate acts as a **Local Agent Bridge**:
- **OpenAI-Compatible Bridge**: Use Codex with any OpenAI-compatible UI by normalizing the Responses API; the built-in Codex conversion also fills and normalizes Codex fingerprint headers such as `User-Agent`, `Version`, `OpenAI-Beta`, and `Originator` so upstream providers see an official Codex CLI-shaped request.
- **Claude Provider Bridge**: Connect Claude Code to OpenAI Chat Completions-compatible providers and Ollama through the built-in local Claude-compatible proxy.
- **OpenCode Provider Control**: Manage OpenCode provider/model selection with a CodexMate-owned provider store under `~/.codexmate`, projecting only the active provider into native OpenCode config to avoid polluting or deleting user-owned settings.
- **Provider Health Cleanup**: Probe local Codex and Claude provider routes, surface failed configs in one modal, and bulk-clean selected broken providers without touching healthy or protected entries.
- **Skills Marketplace**: A local-first market to share and import skills between different agent apps.
- **Prompt File Editor**: Unified editor for global and project-level `CLAUDE.md` and `AGENTS.md` with auto-detection of project paths.
- **Task Orchestrator**: Plan and execute complex tasks with dependency tracking.
Expand All @@ -77,6 +78,7 @@ Unlike simple wrappers, Codex Mate acts as a **Local Agent Bridge**:
| **OpenAI Bridge** | ✅ | Convert Codex Responses API to standard OpenAI format and attach/normalize Codex fingerprints in the built-in conversion |
| **Claude Provider Bridge** | ✅ | Connect Claude Code to OpenAI Chat Completions-compatible providers and Ollama via the built-in Claude-compatible proxy |
| **OpenCode Provider Store** | ✅ | Keep multiple OpenCode providers in `~/.codexmate` while projecting only the selected provider to native OpenCode config |
| **Provider Health Check** | ✅ | Probe local Codex/Claude provider routes, highlight failed configs, and bulk-remove selected broken providers safely |
| **Prompt Templates** | ✅ | Reusable prompt plugins with variables |
| **Prompt File Editor** | ✅ | Edit global and project-level CLAUDE.md / AGENTS.md with auto-detect and path switching |
| **MCP Integration** | ✅ | Expose local tools and resources via MCP stdio |
Expand Down
2 changes: 2 additions & 0 deletions README.vi.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Bạn có bao giờ cảm thấy rối khi phải quản lý nhiều AI agent c
Khác với các wrapper đơn giản, Codex Mate hoạt động như một **Agent Bridge cục bộ**:
- **Trình duyệt phiên thống nhất**: Tìm kiếm và xuất phiên làm việc từ tất cả tool trong một nơi duy nhất.
- **Bridge tương thích OpenAI**: Dùng Codex với bất kỳ UI nào hỗ trợ OpenAI bằng cách chuẩn hóa Responses API.
- **Dọn dẹp provider lỗi**: Kiểm tra route provider cục bộ của Codex/Claude, hiển thị cấu hình lỗi trong một modal và xóa hàng loạt các provider hỏng đã chọn mà không chạm vào mục khỏe mạnh hoặc được bảo vệ.
- **Chợ skill cục bộ**: Chia sẻ và nhập skill giữa các app agent khác nhau.
- **Điều phối tác vụ**: Lập kế hoạch và thực thi tác vụ phức tạp với theo dõi phụ thuộc.

Expand All @@ -64,6 +65,7 @@ Khác với các wrapper đơn giản, Codex Mate hoạt động như một **Ag
| **Chợ skill cục bộ** | ✅ | Import/export skill giữa các app agent |
| **Hàng đợi tác vụ** | ✅ | Thực thi tác vụ theo DAG và xem log |
| **Bridge OpenAI** | ✅ | Chuyển đổi Codex Responses API sang định dạng OpenAI chuẩn |
| **Kiểm tra sức khỏe Provider** | ✅ | Kiểm tra route provider Codex/Claude, đánh dấu cấu hình lỗi và xóa hàng loạt các provider hỏng đã chọn một cách an toàn |
| **Mẫu Prompt** | ✅ | Plugin prompt tái sử dụng được với hỗ trợ biến |
| **Tích hợp MCP** | ✅ | Expose tool và resource cục bộ qua MCP stdio |
| **Tự động cập nhật** | ✅ | Cập nhật nhanh qua `codexmate update` |
Expand Down
2 changes: 2 additions & 0 deletions README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
- **OpenAI 兼容桥接**:通过归一化 Responses API,让 Codex 能够与任何支持 OpenAI 格式的 UI 配合使用;内建 Codex 转换会补齐并规范化 `User-Agent`、`Version`、`OpenAI-Beta`、`Originator` 等 Codex 指纹,对上游伪装为官方 Codex CLI 请求。
- **Claude Provider 桥接**:通过内建本地 Claude 兼容代理,让 Claude Code 接入 OpenAI Chat Completions 兼容 provider 与 Ollama。
- **OpenCode Provider 控制**:在 `~/.codexmate` 下维护 CodexMate 自有的 OpenCode 多 provider 存储,只将当前选中的 provider 投影到 OpenCode 原生配置,避免污染或误删用户已有配置。
- **Provider 健康清理**:探测本地 Codex 与 Claude provider 路由,在同一个弹窗里集中展示失败配置,并批量清理已选坏配置,不影响正常或受保护项。
- **Skills 市场**:本地优先的市场,支持在不同的智能体应用之间共享和导入 Skills。
- **提示词文件编辑器**:统一编辑全局和项目级 `CLAUDE.md` 与 `AGENTS.md`,支持项目路径自动检测。
- **任务编排器**:支持带有依赖跟踪的复杂任务规划与执行。
Expand All @@ -76,6 +77,7 @@
| **OpenAI 桥接** | ✅ | 将 Codex Responses API 转换为标准 OpenAI 格式,并在内建转换中附加/规范化 Codex 指纹 |
| **Claude Provider 桥接** | ✅ | 通过内建 Claude 兼容代理,让 Claude Code 接入 OpenAI Chat Completions 兼容 provider 与 Ollama |
| **OpenCode Provider 存储** | ✅ | 在 `~/.codexmate` 中保留多个 OpenCode provider,只将当前选中的 provider 投影到 OpenCode 原生配置 |
| **Provider 健康检查** | ✅ | 探测本地 Codex/Claude provider 路由,标记失败配置,并安全批量移除已选坏配置 |
| **提示词模板** | ✅ | 支持变量的可复用提示词插件 |
| **提示词文件编辑器** | ✅ | 编辑全局和项目级 CLAUDE.md / AGENTS.md,支持自动检测与路径切换 |
| **MCP 集成** | ✅ | 通过 MCP stdio 暴露本地工具与资源 |
Expand Down
122 changes: 117 additions & 5 deletions cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,15 @@ const PROVIDER_CACHE_FILE_GROUPS = Object.freeze({
'opencode-provider-current-models.json'
]
});
const PROVIDER_CACHE_PROVIDER_FILES = Object.freeze([
'codex-providers.json',
'claude-providers.json',
'opencode-providers.json'
]);
const PROVIDER_CACHE_CURRENT_MODEL_FILES = Object.freeze([
'codex-provider-current-models.json',
'opencode-provider-current-models.json'
]);
const PROVIDER_CACHE_MAX_FILE_BYTES = 256 * 1024;
const CODEXMATE_PREFERENCES_FILE = path.join(CODEXMATE_DIR, 'preferences.json');
const CODEXMATE_OPENCODE_DIR = path.join(CODEXMATE_DIR, 'opencode');
Expand Down Expand Up @@ -1083,7 +1092,8 @@ function getApiToolConfigWriteTarget(action) {
'restore-claude-dir',
'claude-local-bridge-toggle',
'claude-local-bridge-set-excluded',
'claude-local-bridge-sync-providers'
'claude-local-bridge-sync-providers',
'delete-provider-cache-record'
]);
const opencodeWriteActions = new Set([
'apply-opencode-config',
Expand Down Expand Up @@ -2877,6 +2887,99 @@ function normalizeProviderCacheProviderMap(rawProviders) {
return providers;
}

function removeProviderFromProviderCacheContainer(rawProviders, providerName) {
const targetName = typeof providerName === 'string' ? providerName.trim() : '';
if (!targetName) return { value: rawProviders, changed: false };

const matchesProviderName = (name) => String(name || '').trim() === targetName;
if (Array.isArray(rawProviders)) {
const filtered = rawProviders.filter((item) => {
if (!isPlainObject(item)) return true;
const itemName = pickProviderCacheString(item, ['name', 'id', 'provider']);
return !matchesProviderName(itemName);
});
return { value: filtered, changed: filtered.length !== rawProviders.length };
}
if (!isPlainObject(rawProviders)) return { value: rawProviders, changed: false };

const next = { ...rawProviders };
let changed = false;
for (const [name, entry] of Object.entries(rawProviders)) {
const entryName = isPlainObject(entry)
? pickProviderCacheString(entry, ['name', 'id', 'provider'])
: '';
if (matchesProviderName(name) || matchesProviderName(entryName)) {
delete next[name];
changed = true;
}
}
return { value: next, changed };
}

function resolveProviderCacheDeleteGroups(groups) {
const requested = Array.isArray(groups) ? groups : (groups ? [groups] : []);
const normalized = requested
.map((item) => String(item || '').trim().toLowerCase())
.filter((item) => Object.prototype.hasOwnProperty.call(PROVIDER_CACHE_FILE_GROUPS, item));
return normalized.length ? Array.from(new Set(normalized)) : Object.keys(PROVIDER_CACHE_FILE_GROUPS);
}

function removeProviderFromProviderCacheRecords(providerName, options = {}) {
const targetName = typeof providerName === 'string' ? providerName.trim() : '';
const summary = { removed: false, providerFiles: [], currentModelFiles: [] };
if (!targetName) return summary;

const groups = resolveProviderCacheDeleteGroups(options.groups || options.group);
const providerFiles = groups
.flatMap((group) => PROVIDER_CACHE_FILE_GROUPS[group] || [])
.filter((fileName) => PROVIDER_CACHE_PROVIDER_FILES.includes(fileName));
const currentModelFiles = groups
.flatMap((group) => PROVIDER_CACHE_FILE_GROUPS[group] || [])
.filter((fileName) => PROVIDER_CACHE_CURRENT_MODEL_FILES.includes(fileName));

for (const fileName of Array.from(new Set(providerFiles))) {
const existing = readProviderCacheJsonObject(fileName);
if (!isPlainObject(existing) || !Object.prototype.hasOwnProperty.call(existing, 'providers')) continue;
const removed = removeProviderFromProviderCacheContainer(existing.providers, targetName);
if (!removed.changed) continue;
writeProviderCacheJsonObject(fileName, {
...existing,
generatedAt: new Date().toISOString(),
providers: removed.value
});
summary.removed = true;
summary.providerFiles.push(fileName);
}

for (const fileName of Array.from(new Set(currentModelFiles))) {
const existing = readProviderCacheJsonObject(fileName);
if (!isPlainObject(existing) || !Object.prototype.hasOwnProperty.call(existing, targetName)) continue;
const next = { ...existing };
delete next[targetName];
writeProviderCacheJsonObject(fileName, next);
summary.removed = true;
summary.currentModelFiles.push(fileName);
}
return summary;
}

function deleteProviderCacheRecord(params = {}) {
const name = typeof params.name === 'string' ? params.name.trim() : '';
if (!name) return { error: '名称不能为空' };
const group = typeof params.group === 'string' ? params.group.trim().toLowerCase() : '';
const groups = resolveProviderCacheDeleteGroups(group || params.groups);
const summary = removeProviderFromProviderCacheRecords(name, { groups });
return {
success: true,
name,
groups,
removed: summary.removed,
providerFiles: summary.providerFiles,
currentModelFiles: summary.currentModelFiles,
records: readProviderCacheRecords()
};
}

function readClaudeProviderCacheProvider(name) {
const targetName = typeof name === 'string' ? name.trim() : '';
if (!targetName) return null;
Expand Down Expand Up @@ -2953,10 +3056,11 @@ function buildProviderCacheSyncProviders() {
function mergeProviderCacheFile(fileName, nextProviders, buildEntry) {
const existing = readProviderCacheJsonObject(fileName);
const existingProviders = normalizeProviderCacheProviderMap(existing.providers);
const providers = { ...existingProviders };
const providers = {};
for (const provider of nextProviders) {
const previous = isPlainObject(providers[provider.name]) ? providers[provider.name] : {};
providers[provider.name] = { ...previous, ...buildEntry(provider) };
const cachedPrevious = isPlainObject(existingProviders[provider.name]) ? existingProviders[provider.name] : previous;
providers[provider.name] = { ...cachedPrevious, ...buildEntry(provider) };
}
const next = {
...existing,
Expand All @@ -2970,9 +3074,13 @@ function mergeProviderCacheFile(fileName, nextProviders, buildEntry) {

function mergeProviderCacheCurrentModelsFile(fileName, nextProviders) {
const existing = readProviderCacheJsonObject(fileName);
const next = { ...existing };
const next = {};
for (const provider of nextProviders) {
if (provider.model) next[provider.name] = provider.model;
if (provider.model) {
next[provider.name] = provider.model;
} else if (typeof existing[provider.name] === 'string' && existing[provider.name].trim()) {
next[provider.name] = existing[provider.name];
}
}
const displayPath = writeProviderCacheJsonObject(fileName, next);
return { path: displayPath, modelCount: Object.keys(next).length };
Expand Down Expand Up @@ -3213,6 +3321,7 @@ function performProviderDeletion(name, options = {}) {

writeCurrentModels(currentModels);
writeConfig(updatedContent.trimEnd() + lineEnding);
removeProviderFromProviderCacheRecords(name);

return result;
}
Expand Down Expand Up @@ -12387,6 +12496,9 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
case 'sync-provider-cache-records':
result = syncProviderCacheRecords();
break;
case 'delete-provider-cache-record':
result = deleteProviderCacheRecord(params || {});
break;
case 'delete-provider':
result = deleteProviderFromConfig(params || {});
break;
Expand Down
Loading
Loading