codexmate 0.1.12 → 0.1.14
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 +5 -3
- package/README.vi.md +4 -3
- package/README.zh.md +5 -3
- package/cli.js +139 -4
- package/package.json +1 -1
- package/web-ui/app.js +88 -19
- package/web-ui/index.html +2 -1
- package/web-ui/modules/app.methods.agents.mjs +14 -3
- package/web-ui/modules/app.methods.index.mjs +2 -0
- package/web-ui/modules/app.methods.navigation.mjs +1 -1
- package/web-ui/modules/app.methods.pi-config.mjs +354 -0
- package/web-ui/modules/app.methods.session-actions.mjs +4 -4
- package/web-ui/modules/app.methods.startup-claude.mjs +2 -1
- package/web-ui/modules/app.methods.tool-config-permissions.mjs +3 -2
- package/web-ui/modules/app.methods.web-ui-preferences.mjs +2 -1
- package/web-ui/modules/config-mode.computed.mjs +4 -3
- package/web-ui/modules/i18n/locales/en.mjs +43 -0
- package/web-ui/modules/i18n/locales/ja.mjs +43 -0
- package/web-ui/modules/i18n/locales/vi.mjs +43 -0
- package/web-ui/modules/i18n/locales/zh-tw.mjs +43 -0
- package/web-ui/modules/i18n/locales/zh.mjs +43 -0
- package/web-ui/partials/index/layout-header.html +17 -2
- package/web-ui/partials/index/panel-config-pi.html +171 -0
- package/web-ui/partials/index/panel-settings.html +7 -0
- package/web-ui/res/web-ui-render.precompiled.js +567 -49
- package/web-ui/styles/settings-panel.css +7 -0
package/README.md
CHANGED
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
|
|
50
50
|
Have you ever felt overwhelmed by managing multiple local AI agents? Each has its own config format, session storage, and skills directory.
|
|
51
51
|
|
|
52
|
-
**Codex Mate** offers a unified control plane to bring order to the chaos. It's a local-first CLI + Web UI designed to manage [Codex](https://github.com/openai/codex), [Claude Code](https://github.com/anthropic-ai/claude-code), [OpenCode](https://opencode.ai/), KiloCode,
|
|
52
|
+
**Codex Mate** offers a unified control plane to bring order to the chaos. It's a local-first CLI + Web UI designed to manage [Codex](https://github.com/openai/codex), [Claude Code](https://github.com/anthropic-ai/claude-code), [OpenCode](https://opencode.ai/), KiloCode, [OpenClaw](https://github.com/moeru-ai/openclaw), and Pi seamlessly.
|
|
53
53
|
|
|
54
54
|
### What's So Special?
|
|
55
55
|
|
|
@@ -59,6 +59,7 @@ Unlike simple wrappers, Codex Mate acts as a **Local Agent Bridge**:
|
|
|
59
59
|
- **Claude Provider Bridge**: Connect Claude Code to OpenAI Chat Completions-compatible providers and Ollama through the built-in local Claude-compatible proxy.
|
|
60
60
|
- **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.
|
|
61
61
|
- **KiloCode Config Bridge**: Configure KiloCode providers from the CLI or Web UI, writing to `~/.config/kilo/kilo.jsonc` while preserving stored API keys.
|
|
62
|
+
- **Pi Provider Config**: Manage Pi providers from the Web UI with lossless round-trip writes to `~/.pi/agent/models.json`, auto-fetched remote model lists, and one-click default provider/model switching via `~/.pi/agent/settings.json`.
|
|
62
63
|
- **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.
|
|
63
64
|
- **Skills Marketplace**: A local-first market to share and import skills between different agent apps.
|
|
64
65
|
- **Prompt File Editor**: Unified editor for global and project-level `CLAUDE.md` and `AGENTS.md` with auto-detection of project paths, plus a shared preset pool for reusable prompts.
|
|
@@ -70,7 +71,7 @@ Unlike simple wrappers, Codex Mate acts as a **Local Agent Bridge**:
|
|
|
70
71
|
|
|
71
72
|
| Feature | Status | Description |
|
|
72
73
|
| --- | --- | --- |
|
|
73
|
-
| **Provider Management** | ✅ | Switch providers/models for Codex, Claude, OpenCode, KiloCode, and
|
|
74
|
+
| **Provider Management** | ✅ | Switch providers/models for Codex, Claude, OpenCode, KiloCode, OpenClaw, and Pi |
|
|
74
75
|
| **Live Agent Sync** | ✅ | Real-time monitoring of Codex/Claude config & status |
|
|
75
76
|
| **Session Browser** | ✅ | Search, preview, filter, and export sessions across Codex, Claude Code, Gemini CLI, CodeBuddy Code, and Pi |
|
|
76
77
|
| **Usage Analytics** | ✅ | Visualize message trends and top projects |
|
|
@@ -80,6 +81,7 @@ Unlike simple wrappers, Codex Mate acts as a **Local Agent Bridge**:
|
|
|
80
81
|
| **Claude Provider Bridge** | ✅ | Connect Claude Code to OpenAI Chat Completions-compatible providers and Ollama via the built-in Claude-compatible proxy |
|
|
81
82
|
| **OpenCode Provider Store** | ✅ | Keep multiple OpenCode providers in `~/.codexmate` while projecting only the selected provider to native OpenCode config |
|
|
82
83
|
| **KiloCode Config Bridge** | ✅ | Configure KiloCode through `codexmate kilo config` or the Web UI, writing provider URL/model/API key data to `~/.config/kilo/kilo.jsonc` |
|
|
84
|
+
| **Pi Provider Config** | ✅ | Manage Pi providers in the Web UI writing to `~/.pi/agent/models.json` (unknown fields preserved) and switch the default provider/model in `~/.pi/agent/settings.json` |
|
|
83
85
|
| **Provider Health Check** | ✅ | Probe local Codex/Claude provider routes, highlight failed configs, and bulk-remove selected broken providers safely |
|
|
84
86
|
| **Prompt Templates** | ✅ | Reusable prompt plugins with variables |
|
|
85
87
|
| **Prompt File Editor** | ✅ | Edit global and project-level CLAUDE.md / AGENTS.md with auto-detect, path switching, and a shared preset pool. Applying a preset only updates the editor; save manually to write the file. |
|
|
@@ -133,7 +135,7 @@ curl -fsSL https://raw.githubusercontent.com/SakuraByteCore/codexmate/main/scrip
|
|
|
133
135
|
- **KiloCode**: `npm install -g @kilocode/cli` (`kilo` / `kilocode`)
|
|
134
136
|
- **OpenCode**: install from the [official OpenCode docs](https://opencode.ai/)
|
|
135
137
|
- **OpenClaw**: see the [OpenClaw repository](https://github.com/moeru-ai/openclaw)
|
|
136
|
-
- **Pi**: sessions are discovered from `~/.pi/agent/sessions`
|
|
138
|
+
- **Pi**: sessions are discovered from `~/.pi/agent/sessions`; provider config lives in `~/.pi/agent/models.json` and the default provider/model in `~/.pi/agent/settings.json`
|
|
137
139
|
|
|
138
140
|
---
|
|
139
141
|
|
package/README.vi.md
CHANGED
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
|
|
42
42
|
Bạn có bao giờ cảm thấy rối khi phải quản lý nhiều AI agent cục bộ? Mỗi tool lại có định dạng cấu hình, nơi lưu phiên và thư mục skill riêng.
|
|
43
43
|
|
|
44
|
-
**Codex Mate** cung cấp một trung tâm điều khiển thống nhất để giải quyết sự hỗn loạn đó. Đây là CLI + Web UI ưu tiên cục bộ, được thiết kế để quản lý [Codex](https://github.com/openai/codex), [Claude Code](https://github.com/anthropic-ai/claude-code), [OpenCode](https://opencode.ai/), KiloCode
|
|
44
|
+
**Codex Mate** cung cấp một trung tâm điều khiển thống nhất để giải quyết sự hỗn loạn đó. Đây là CLI + Web UI ưu tiên cục bộ, được thiết kế để quản lý [Codex](https://github.com/openai/codex), [Claude Code](https://github.com/anthropic-ai/claude-code), [OpenCode](https://opencode.ai/), KiloCode, [OpenClaw](https://github.com/moeru-ai/openclaw) và Pi một cách liền mạch.
|
|
45
45
|
|
|
46
46
|
### Điểm nổi bật
|
|
47
47
|
|
|
@@ -58,7 +58,8 @@ Khác với các wrapper đơn giản, Codex Mate hoạt động như một **Ag
|
|
|
58
58
|
|
|
59
59
|
| Tính năng | Trạng thái | Mô tả |
|
|
60
60
|
| --- | --- | --- |
|
|
61
|
-
| **Quản lý Provider** | ✅ | Chuyển đổi provider/model cho Codex, Claude, OpenCode, KiloCode và
|
|
61
|
+
| **Quản lý Provider** | ✅ | Chuyển đổi provider/model cho Codex, Claude, OpenCode, KiloCode, OpenClaw và Pi |
|
|
62
|
+
| **Cấu hình Provider Pi** | ✅ | Quản lý provider Pi trong Web UI, ghi vào `~/.pi/agent/models.json` (giữ nguyên các trường không nhận dạng được) và chuyển đổi provider/model mặc định trong `~/.pi/agent/settings.json` |
|
|
62
63
|
| **Đồng bộ Agent trực tiếp** | ✅ | Giám sát cấu hình & trạng thái Codex/Claude theo thời gian thực |
|
|
63
64
|
| **Trình duyệt phiên** | ✅ | Liệt kê, xem trước, lọc và xuất phiên (Codex/Claude/Gemini/CodeBuddy/Pi) |
|
|
64
65
|
| **Phân tích sử dụng** | ✅ | Trực quan hóa xu hướng tin nhắn và dự án nổi bật |
|
|
@@ -118,7 +119,7 @@ curl -fsSL https://raw.githubusercontent.com/SakuraByteCore/codexmate/main/scrip
|
|
|
118
119
|
- **KiloCode**: `npm install -g @kilocode/cli` (`kilo` / `kilocode`)
|
|
119
120
|
- **OpenCode**: xem [tài liệu chính thức OpenCode](https://opencode.ai/)
|
|
120
121
|
- **OpenClaw**: xem [kho lưu trữ OpenClaw](https://github.com/moeru-ai/openclaw)
|
|
121
|
-
- **Pi**: phiên làm việc được phát hiện từ `~/.pi/agent/sessions`
|
|
122
|
+
- **Pi**: phiên làm việc được phát hiện từ `~/.pi/agent/sessions`; cấu hình provider nằm ở `~/.pi/agent/models.json`, provider/model mặc định nằm ở `~/.pi/agent/settings.json`
|
|
122
123
|
|
|
123
124
|
---
|
|
124
125
|
|
package/README.zh.md
CHANGED
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
|
|
49
49
|
你是否曾因管理多个本地 AI 智能体而感到疲惫?每个工具都有自己的配置格式、会话存储和 Skills 目录。
|
|
50
50
|
|
|
51
|
-
**Codex Mate** 提供了一个统一的控制平面,让混乱重归有序。这是一个本地优先的 CLI + Web UI,旨在无缝管理 [Codex](https://github.com/openai/codex)、[Claude Code](https://github.com/anthropic-ai/claude-code)、[OpenCode](https://opencode.ai/)、KiloCode
|
|
51
|
+
**Codex Mate** 提供了一个统一的控制平面,让混乱重归有序。这是一个本地优先的 CLI + Web UI,旨在无缝管理 [Codex](https://github.com/openai/codex)、[Claude Code](https://github.com/anthropic-ai/claude-code)、[OpenCode](https://opencode.ai/)、KiloCode、[OpenClaw](https://github.com/moeru-ai/openclaw) 与 Pi。
|
|
52
52
|
|
|
53
53
|
### 有什么独特之处?
|
|
54
54
|
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
- **OpenAI 兼容桥接**:通过归一化 Responses API,让 Codex 能够与任何支持 OpenAI 格式的 UI 配合使用;内建 Codex 转换会补齐并规范化 `User-Agent`、`Version`、`OpenAI-Beta`、`Originator` 等 Codex 指纹,对上游伪装为官方 Codex CLI 请求。
|
|
58
58
|
- **Claude Provider 桥接**:通过内建本地 Claude 兼容代理,让 Claude Code 接入 OpenAI Chat Completions 兼容 provider 与 Ollama。
|
|
59
59
|
- **OpenCode Provider 控制**:在 `~/.codexmate` 下维护 CodexMate 自有的 OpenCode 多 provider 存储,只将当前选中的 provider 投影到 OpenCode 原生配置,避免污染或误删用户已有配置。
|
|
60
|
+
- **Pi Provider 配置**:在 Web UI 中管理 Pi 供应商,写入 `~/.pi/agent/models.json` 时无损保留未知字段,支持自动拉取远端模型列表,并可一键切换 `~/.pi/agent/settings.json` 中的默认供应商/模型。
|
|
60
61
|
- **Provider 健康清理**:探测本地 Codex 与 Claude provider 路由,在同一个弹窗里集中展示失败配置,并批量清理已选坏配置,不影响正常或受保护项。
|
|
61
62
|
- **Skills 市场**:本地优先的市场,支持在不同的智能体应用之间共享和导入 Skills。
|
|
62
63
|
- **提示词文件编辑器**:统一编辑全局和项目级 `CLAUDE.md` 与 `AGENTS.md`,支持项目路径自动检测,并提供通用预设池复用 prompt。
|
|
@@ -68,7 +69,7 @@
|
|
|
68
69
|
|
|
69
70
|
| 特性 | 状态 | 描述 |
|
|
70
71
|
| --- | --- | --- |
|
|
71
|
-
| **Provider 管理** | ✅ | 切换 Codex、Claude、OpenCode、KiloCode
|
|
72
|
+
| **Provider 管理** | ✅ | 切换 Codex、Claude、OpenCode、KiloCode、OpenClaw 与 Pi 的 provider/model |
|
|
72
73
|
| **状态实时同步** | ✅ | 实时感知 Codex/Claude 的配置与运行状态变更 |
|
|
73
74
|
| **会话浏览器** | ✅ | 跨 Codex、Claude Code、Gemini CLI、CodeBuddy Code 与 Pi 的本地会话进行检索、预览、筛选与导出 |
|
|
74
75
|
| **Usage 统计** | ✅ | 可视化消息趋势与热门项目统计 |
|
|
@@ -77,6 +78,7 @@
|
|
|
77
78
|
| **OpenAI 桥接** | ✅ | 将 Codex Responses API 转换为标准 OpenAI 格式,并在内建转换中附加/规范化 Codex 指纹 |
|
|
78
79
|
| **Claude Provider 桥接** | ✅ | 通过内建 Claude 兼容代理,让 Claude Code 接入 OpenAI Chat Completions 兼容 provider 与 Ollama |
|
|
79
80
|
| **OpenCode Provider 存储** | ✅ | 在 `~/.codexmate` 中保留多个 OpenCode provider,只将当前选中的 provider 投影到 OpenCode 原生配置 |
|
|
81
|
+
| **Pi Provider 配置** | ✅ | 在 Web UI 中管理 Pi 供应商,写入 `~/.pi/agent/models.json`(未知字段无损保留),并切换 `~/.pi/agent/settings.json` 中的默认供应商/模型 |
|
|
80
82
|
| **Provider 健康检查** | ✅ | 探测本地 Codex/Claude provider 路由,标记失败配置,并安全批量移除已选坏配置 |
|
|
81
83
|
| **提示词模板** | ✅ | 支持变量的可复用提示词插件 |
|
|
82
84
|
| **提示词文件编辑器** | ✅ | 编辑全局和项目级 CLAUDE.md / AGENTS.md,支持自动检测、路径切换与通用预设池。应用预设只更新编辑器,需手动保存才会写入文件。 |
|
|
@@ -130,7 +132,7 @@ curl -fsSL https://raw.githubusercontent.com/SakuraByteCore/codexmate/main/scrip
|
|
|
130
132
|
- **KiloCode**: `npm install -g @kilocode/cli`(`kilo` / `kilocode`)
|
|
131
133
|
- **OpenCode**: 参照 [OpenCode 官方文档](https://opencode.ai/) 安装
|
|
132
134
|
- **OpenClaw**: 参见 [OpenClaw 仓库](https://github.com/moeru-ai/openclaw)
|
|
133
|
-
- **Pi**: 会话从 `~/.pi/agent/sessions`
|
|
135
|
+
- **Pi**: 会话从 `~/.pi/agent/sessions` 自动发现;供应商配置位于 `~/.pi/agent/models.json`,默认供应商/模型位于 `~/.pi/agent/settings.json`
|
|
134
136
|
|
|
135
137
|
---
|
|
136
138
|
|
package/cli.js
CHANGED
|
@@ -891,8 +891,8 @@ function isPlainObject(value) {
|
|
|
891
891
|
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
892
892
|
}
|
|
893
893
|
|
|
894
|
-
const TOOL_CONFIG_PERMISSION_TARGETS = new Set(['codex', 'claude', 'opencode', 'kilocode', 'openclaw']);
|
|
895
|
-
const TOOL_CONFIG_PERMISSION_DEFAULTS = Object.freeze({ codex: false, claude: false, opencode: false, kilocode: false, openclaw: false });
|
|
894
|
+
const TOOL_CONFIG_PERMISSION_TARGETS = new Set(['codex', 'claude', 'opencode', 'kilocode', 'openclaw', 'pi']);
|
|
895
|
+
const TOOL_CONFIG_PERMISSION_DEFAULTS = Object.freeze({ codex: false, claude: false, opencode: false, kilocode: false, openclaw: false, pi: false });
|
|
896
896
|
let toolConfigWriteGuardDepth = 0;
|
|
897
897
|
|
|
898
898
|
function enterToolConfigWriteGuard() {
|
|
@@ -921,7 +921,8 @@ function normalizeToolConfigPermissions(value) {
|
|
|
921
921
|
claude: source.claude === true,
|
|
922
922
|
opencode: source.opencode === true,
|
|
923
923
|
kilocode: source.kilocode === true,
|
|
924
|
-
openclaw: source.openclaw === true
|
|
924
|
+
openclaw: source.openclaw === true,
|
|
925
|
+
pi: source.pi === true
|
|
925
926
|
};
|
|
926
927
|
}
|
|
927
928
|
|
|
@@ -979,7 +980,7 @@ function normalizeMainTabPreference(value) {
|
|
|
979
980
|
|
|
980
981
|
function normalizeConfigModePreference(value) {
|
|
981
982
|
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
982
|
-
return ['codex', 'claude', 'openclaw', 'opencode', 'kilocode'].includes(normalized) ? normalized : 'codex';
|
|
983
|
+
return ['codex', 'claude', 'openclaw', 'opencode', 'kilocode', 'pi'].includes(normalized) ? normalized : 'codex';
|
|
983
984
|
}
|
|
984
985
|
|
|
985
986
|
function normalizeUsageTimeRangePreference(value) {
|
|
@@ -1203,9 +1204,123 @@ function getApiToolConfigWriteTarget(action) {
|
|
|
1203
1204
|
if (opencodeWriteActions.has(name)) return 'opencode';
|
|
1204
1205
|
if (kilocodeWriteActions.has(name)) return 'kilocode';
|
|
1205
1206
|
if (openclawWriteActions.has(name)) return 'openclaw';
|
|
1207
|
+
const piWriteActions = new Set([
|
|
1208
|
+
'write-pi-models',
|
|
1209
|
+
'write-pi-settings'
|
|
1210
|
+
]);
|
|
1211
|
+
if (piWriteActions.has(name)) return 'pi';
|
|
1206
1212
|
return '';
|
|
1207
1213
|
}
|
|
1208
1214
|
|
|
1215
|
+
function getPiAgentDir() {
|
|
1216
|
+
const homeDir = (typeof os.homedir === 'function' ? os.homedir() : null) || process.env.HOME || process.env.USERPROFILE || '';
|
|
1217
|
+
return path.join(homeDir, '.pi', 'agent');
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
function readPiModels(params = {}) {
|
|
1221
|
+
try {
|
|
1222
|
+
const dir = getPiAgentDir();
|
|
1223
|
+
const filePath = path.join(dir, 'models.json');
|
|
1224
|
+
if (!fs.existsSync(filePath)) {
|
|
1225
|
+
return { providers: {} };
|
|
1226
|
+
}
|
|
1227
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
1228
|
+
const data = JSON.parse(raw);
|
|
1229
|
+
const providers = (data && typeof data === 'object' && data.providers && typeof data.providers === 'object')
|
|
1230
|
+
? data.providers
|
|
1231
|
+
: {};
|
|
1232
|
+
return { providers };
|
|
1233
|
+
} catch (e) {
|
|
1234
|
+
return { error: e && e.message ? e.message : '读取 Pi models.json 失败' };
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
function writePiModels(params = {}) {
|
|
1239
|
+
const providers = params && typeof params.providers === 'object' ? params.providers : null;
|
|
1240
|
+
if (providers === null) {
|
|
1241
|
+
return { error: 'providers 不能为空' };
|
|
1242
|
+
}
|
|
1243
|
+
try {
|
|
1244
|
+
const dir = getPiAgentDir();
|
|
1245
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1246
|
+
const filePath = path.join(dir, 'models.json');
|
|
1247
|
+
let existing = {};
|
|
1248
|
+
if (fs.existsSync(filePath)) {
|
|
1249
|
+
try {
|
|
1250
|
+
existing = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
1251
|
+
} catch (_) {
|
|
1252
|
+
existing = {};
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
existing.providers = providers;
|
|
1256
|
+
fs.writeFileSync(filePath, JSON.stringify(existing, null, 2), 'utf-8');
|
|
1257
|
+
return { success: true };
|
|
1258
|
+
} catch (e) {
|
|
1259
|
+
return { error: e && e.message ? e.message : '写入 Pi models.json 失败' };
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
async function fetchPiRemoteModels(params = {}) {
|
|
1264
|
+
const baseUrl = typeof params.baseUrl === 'string' ? params.baseUrl.trim() : '';
|
|
1265
|
+
const apiKey = typeof params.apiKey === 'string' ? params.apiKey.trim() : '';
|
|
1266
|
+
if (!baseUrl || !isValidHttpUrl(baseUrl)) {
|
|
1267
|
+
return { error: 'baseUrl 无效', models: [] };
|
|
1268
|
+
}
|
|
1269
|
+
try {
|
|
1270
|
+
const result = await fetchModelsFromBaseUrl(baseUrl, apiKey);
|
|
1271
|
+
const models = result && Array.isArray(result.models)
|
|
1272
|
+
? [...new Set(result.models.filter((id) => typeof id === 'string' && id !== ''))].sort()
|
|
1273
|
+
: [];
|
|
1274
|
+
if (models.length > 0) return { models };
|
|
1275
|
+
return { error: (result && result.error) || '未获取到可用模型', models: [] };
|
|
1276
|
+
} catch (e) {
|
|
1277
|
+
return { error: e && e.message ? e.message : '请求失败', models: [] };
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
function readPiSettings(params = {}) {
|
|
1282
|
+
try {
|
|
1283
|
+
const dir = getPiAgentDir();
|
|
1284
|
+
const filePath = path.join(dir, 'settings.json');
|
|
1285
|
+
if (!fs.existsSync(filePath)) {
|
|
1286
|
+
return { settings: {} };
|
|
1287
|
+
}
|
|
1288
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
1289
|
+
const data = JSON.parse(raw);
|
|
1290
|
+
return { settings: data || {} };
|
|
1291
|
+
} catch (e) {
|
|
1292
|
+
return { error: e && e.message ? e.message : '读取 Pi settings.json 失败' };
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
function writePiSettings(params = {}) {
|
|
1297
|
+
const updates = {};
|
|
1298
|
+
if (typeof (params && params.defaultProvider) === 'string') updates.defaultProvider = params.defaultProvider;
|
|
1299
|
+
if (typeof (params && params.defaultModel) === 'string') updates.defaultModel = params.defaultModel;
|
|
1300
|
+
if (Object.keys(updates).length === 0) {
|
|
1301
|
+
return { error: '缺少可写入的设置项' };
|
|
1302
|
+
}
|
|
1303
|
+
try {
|
|
1304
|
+
const dir = getPiAgentDir();
|
|
1305
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1306
|
+
const filePath = path.join(dir, 'settings.json');
|
|
1307
|
+
let existing = {};
|
|
1308
|
+
if (fs.existsSync(filePath)) {
|
|
1309
|
+
try {
|
|
1310
|
+
existing = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
1311
|
+
} catch (_) {
|
|
1312
|
+
existing = {};
|
|
1313
|
+
}
|
|
1314
|
+
if (!existing || typeof existing !== 'object' || Array.isArray(existing)) existing = {};
|
|
1315
|
+
}
|
|
1316
|
+
const updated = { ...existing, ...updates };
|
|
1317
|
+
fs.writeFileSync(filePath, JSON.stringify(updated, null, 2), 'utf-8');
|
|
1318
|
+
return { success: true, settings: updated };
|
|
1319
|
+
} catch (e) {
|
|
1320
|
+
return { error: e && e.message ? e.message : '写入 Pi settings.json 失败' };
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1209
1324
|
function setToolConfigPermission(params = {}) {
|
|
1210
1325
|
const target = normalizeToolConfigTarget(params && params.target);
|
|
1211
1326
|
if (!target) return { error: '未知配置对象' };
|
|
@@ -13173,6 +13288,26 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
13173
13288
|
};
|
|
13174
13289
|
break;
|
|
13175
13290
|
}
|
|
13291
|
+
case 'read-pi-models': {
|
|
13292
|
+
result = readPiModels(params || {});
|
|
13293
|
+
break;
|
|
13294
|
+
}
|
|
13295
|
+
case 'write-pi-models': {
|
|
13296
|
+
result = writePiModels(params || {});
|
|
13297
|
+
break;
|
|
13298
|
+
}
|
|
13299
|
+
case 'read-pi-settings': {
|
|
13300
|
+
result = readPiSettings(params || {});
|
|
13301
|
+
break;
|
|
13302
|
+
}
|
|
13303
|
+
case 'write-pi-settings': {
|
|
13304
|
+
result = writePiSettings(params || {});
|
|
13305
|
+
break;
|
|
13306
|
+
}
|
|
13307
|
+
case 'fetch-pi-remote-models': {
|
|
13308
|
+
result = await fetchPiRemoteModels(params || {});
|
|
13309
|
+
break;
|
|
13310
|
+
}
|
|
13176
13311
|
case 'install-status':
|
|
13177
13312
|
result = buildInstallStatusReport();
|
|
13178
13313
|
break;
|
package/package.json
CHANGED
package/web-ui/app.js
CHANGED
|
@@ -26,6 +26,35 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
const { createApp } = Vue;
|
|
29
|
+
const showFatalErrorOverlay = (label, message, stack, extra) => {
|
|
30
|
+
try {
|
|
31
|
+
const target = document.querySelector('#app') || document.body;
|
|
32
|
+
const pre = document.createElement('pre');
|
|
33
|
+
pre.style.cssText = 'position:fixed;bottom:0;left:0;right:0;background:#900;color:#fff;padding:12px;white-space:pre-wrap;font-size:12px;z-index:9999;';
|
|
34
|
+
pre.textContent = '[' + label + '] ' + (message || '') + '\n' + (stack || '') + (extra ? '\n' + extra : '');
|
|
35
|
+
target.appendChild(pre);
|
|
36
|
+
} catch (_) {}
|
|
37
|
+
};
|
|
38
|
+
window.addEventListener('error', (event) => {
|
|
39
|
+
console.error('[window error]', event.message, event.filename, event.lineno, event.colno, event.error);
|
|
40
|
+
try {
|
|
41
|
+
const target = document.querySelector('#app') || document.body;
|
|
42
|
+
const pre = document.createElement('pre');
|
|
43
|
+
pre.style.cssText = 'position:fixed;bottom:60px;left:0;right:0;background:#b00;color:#fff;padding:12px;white-space:pre-wrap;font-size:12px;z-index:9999;';
|
|
44
|
+
pre.textContent = '[window error] ' + (event.message || '') + '\n' + ((event.error && event.error.stack) || (event.filename + ':' + event.lineno + ':' + event.colno));
|
|
45
|
+
target.appendChild(pre);
|
|
46
|
+
} catch (_) {}
|
|
47
|
+
});
|
|
48
|
+
window.addEventListener('unhandledrejection', (event) => {
|
|
49
|
+
console.error('[unhandled rejection]', event.reason);
|
|
50
|
+
try {
|
|
51
|
+
const target = document.querySelector('#app') || document.body;
|
|
52
|
+
const pre = document.createElement('pre');
|
|
53
|
+
pre.style.cssText = 'position:fixed;bottom:120px;left:0;right:0;background:#b00;color:#fff;padding:12px;white-space:pre-wrap;font-size:12px;z-index:9999;';
|
|
54
|
+
pre.textContent = '[unhandled rejection] ' + (event.reason && event.reason.message ? event.reason.message : String(event.reason)) + '\n' + ((event.reason && event.reason.stack) || '');
|
|
55
|
+
target.appendChild(pre);
|
|
56
|
+
} catch (_) {}
|
|
57
|
+
});
|
|
29
58
|
|
|
30
59
|
const appOptions = {
|
|
31
60
|
data() {
|
|
@@ -96,6 +125,29 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
96
125
|
showCodexBridgePoolModal: false,
|
|
97
126
|
showClaudeBridgePoolModal: false,
|
|
98
127
|
showWebhookModal: false,
|
|
128
|
+
piProviders: {},
|
|
129
|
+
piProviderIds: [],
|
|
130
|
+
editingPiProvider: null,
|
|
131
|
+
addingPiProviderId: '',
|
|
132
|
+
addingPiProviderName: '',
|
|
133
|
+
addingPiProviderBaseUrl: '',
|
|
134
|
+
addingPiProviderApi: 'openai-completions',
|
|
135
|
+
addingPiProviderApiKey: '',
|
|
136
|
+
addingPiProviderModel: '',
|
|
137
|
+
addingPiRemoteModels: [],
|
|
138
|
+
addingPiRemoteLoading: false,
|
|
139
|
+
addingPiRemoteError: '',
|
|
140
|
+
addingPiRemoteDebounce: null,
|
|
141
|
+
showAddPiProviderModal: false,
|
|
142
|
+
piProviderLoading: false,
|
|
143
|
+
piSaving: false,
|
|
144
|
+
piRemoteModels: [],
|
|
145
|
+
piRemoteModelsLoading: false,
|
|
146
|
+
piRemoteModelError: '',
|
|
147
|
+
piModelSearch: '',
|
|
148
|
+
piActiveProvider: '',
|
|
149
|
+
piActiveModel: '',
|
|
150
|
+
piShowKey: false,
|
|
99
151
|
// Plugins
|
|
100
152
|
pluginsActiveId: 'prompt-templates',
|
|
101
153
|
pluginsLoading: false,
|
|
@@ -137,7 +189,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
137
189
|
configTemplateDiffFingerprint: '',
|
|
138
190
|
_configTemplateDiffPreviewRequestToken: null,
|
|
139
191
|
configTemplateDiffConfirmEnabled: true,
|
|
140
|
-
configModeVisibility: { codex: true, claude: true, openclaw: true, opencode: true, kilocode: true },
|
|
192
|
+
configModeVisibility: { codex: true, claude: true, openclaw: true, opencode: true, kilocode: true, pi: true },
|
|
141
193
|
codexApplying: false,
|
|
142
194
|
_pendingCodexApplyOptions: null,
|
|
143
195
|
agentsContent: '',
|
|
@@ -433,8 +485,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
433
485
|
providerCacheError: '',
|
|
434
486
|
providerCacheRequestSeq: 0,
|
|
435
487
|
settingsTab: 'general',
|
|
436
|
-
toolConfigPermissions: { codex: false, claude: false, opencode: false, kilocode: false, openclaw: false },
|
|
437
|
-
toolConfigPermissionSaving: { codex: false, claude: false, opencode: false, kilocode: false, openclaw: false },
|
|
488
|
+
toolConfigPermissions: { codex: false, claude: false, opencode: false, kilocode: false, openclaw: false, pi: false },
|
|
489
|
+
toolConfigPermissionSaving: { codex: false, claude: false, opencode: false, kilocode: false, openclaw: false, pi: false },
|
|
438
490
|
sessionTrashEnabled: true,
|
|
439
491
|
sessionTrashItems: [],
|
|
440
492
|
sessionTrashVisibleCount: SESSION_TRASH_PAGE_SIZE,
|
|
@@ -535,7 +587,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
535
587
|
return true;
|
|
536
588
|
}
|
|
537
589
|
})();
|
|
538
|
-
|
|
590
|
+
this.loadWebUiPreferences({ applyNavigation: applyPreferenceNavigation }).then(() => {
|
|
591
|
+
if (this.mainTab === 'prompts' && typeof this.loadPromptsTabContent === 'function') {
|
|
592
|
+
this.loadPromptsTabContent();
|
|
593
|
+
}
|
|
594
|
+
});
|
|
539
595
|
}
|
|
540
596
|
if (typeof this.t === 'function') {
|
|
541
597
|
this.confirmDialogConfirmText = this.t('confirm.ok');
|
|
@@ -734,14 +790,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
734
790
|
return;
|
|
735
791
|
}
|
|
736
792
|
if (newTab === 'prompts') {
|
|
737
|
-
if (
|
|
738
|
-
this.loadProjectPathOptions();
|
|
739
|
-
}
|
|
740
|
-
if (this.promptsSubTab === 'system') {
|
|
741
|
-
if (typeof this.loadSystemPrompt === 'function') this.loadSystemPrompt();
|
|
742
|
-
} else if (typeof this.loadPromptsContent === 'function') {
|
|
743
|
-
this.loadPromptsContent();
|
|
744
|
-
}
|
|
793
|
+
if (typeof this.loadPromptsTabContent === 'function') this.loadPromptsTabContent();
|
|
745
794
|
}
|
|
746
795
|
},
|
|
747
796
|
promptsSubTab(newVal) {
|
|
@@ -753,11 +802,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
753
802
|
return;
|
|
754
803
|
}
|
|
755
804
|
if (this.mainTab === 'prompts') {
|
|
756
|
-
if (this.
|
|
757
|
-
if (typeof this.loadSystemPrompt === 'function') this.loadSystemPrompt();
|
|
758
|
-
} else if (typeof this.loadPromptsContent === 'function') {
|
|
759
|
-
this.loadPromptsContent();
|
|
760
|
-
}
|
|
805
|
+
if (typeof this.loadPromptsTabContent === 'function') this.loadPromptsTabContent();
|
|
761
806
|
}
|
|
762
807
|
},
|
|
763
808
|
projectClaudeMdPath(newPath) {
|
|
@@ -777,8 +822,19 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
777
822
|
}
|
|
778
823
|
},
|
|
779
824
|
|
|
825
|
+
watch: {
|
|
826
|
+
configMode(newMode) {
|
|
827
|
+
if (newMode === 'pi' && typeof this.loadPiSources === 'function') {
|
|
828
|
+
this.loadPiSources();
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
},
|
|
832
|
+
|
|
780
833
|
computed: createAppComputed(),
|
|
781
|
-
methods:
|
|
834
|
+
methods: {
|
|
835
|
+
...createAppMethods(),
|
|
836
|
+
...(typeof createPiConfigMethods === 'function' ? createPiConfigMethods({ api }) : {})
|
|
837
|
+
}
|
|
782
838
|
};
|
|
783
839
|
|
|
784
840
|
if (typeof window.__CODEXMATE_WEB_UI_RENDER__ === 'function') {
|
|
@@ -786,6 +842,19 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
786
842
|
}
|
|
787
843
|
|
|
788
844
|
const app = createApp(appOptions);
|
|
845
|
+
app.config.errorHandler = (err, vm, info) => {
|
|
846
|
+
console.error('[Vue error handler]', err, info);
|
|
847
|
+
if (err && err.stack) console.error(err.stack);
|
|
848
|
+
showFatalErrorOverlay('Vue error', err && err.message ? err.message : String(err), err && err.stack ? err.stack : '', info || '');
|
|
849
|
+
};
|
|
789
850
|
|
|
790
|
-
|
|
851
|
+
try {
|
|
852
|
+
app.mount('#app');
|
|
853
|
+
} catch (error) {
|
|
854
|
+
console.error('Failed to mount Web UI:', error);
|
|
855
|
+
const fallback = document.querySelector('#app');
|
|
856
|
+
if (fallback) {
|
|
857
|
+
fallback.innerHTML = '<pre style="color:red;white-space:pre-wrap;">Failed to mount Web UI\n' + (error && error.stack ? error.stack : String(error)) + '</pre>';
|
|
858
|
+
}
|
|
859
|
+
}
|
|
791
860
|
});
|
package/web-ui/index.html
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
<!-- @include ./partials/index/panel-config-openclaw.html -->
|
|
19
19
|
<!-- @include ./partials/index/panel-config-opencode.html -->
|
|
20
20
|
<!-- @include ./partials/index/panel-config-kilocode.html -->
|
|
21
|
+
<!-- @include ./partials/index/panel-config-pi.html -->
|
|
21
22
|
<!-- @include ./partials/index/panel-sessions.html -->
|
|
22
23
|
<!-- @include ./partials/index/panel-usage.html -->
|
|
23
24
|
<!-- @include ./partials/index/panel-docs.html -->
|
|
@@ -34,6 +35,6 @@
|
|
|
34
35
|
<!-- @include ./partials/index/modal-skills.html -->
|
|
35
36
|
<!-- @include ./partials/index/modal-health-check.html -->
|
|
36
37
|
<!-- @include ./partials/index/modal-confirm-toast.html -->
|
|
37
|
-
<script type="module" src="/web-ui/app.js"></script>
|
|
38
|
+
<script type="module" src="/web-ui/app.js?cb=3"></script>
|
|
38
39
|
</body>
|
|
39
40
|
</html>
|
|
@@ -980,17 +980,28 @@ export function createAgentsMethods(options = {}) {
|
|
|
980
980
|
this.loadProjectPathOptions();
|
|
981
981
|
}
|
|
982
982
|
if (this.promptsSubTab === normalized) {
|
|
983
|
-
if (
|
|
984
|
-
else this.loadPromptsContent();
|
|
983
|
+
if (typeof this.loadPromptsTabContent === 'function') this.loadPromptsTabContent();
|
|
985
984
|
return;
|
|
986
985
|
}
|
|
986
|
+
this.__skipNextPromptsSubTabLoad = true;
|
|
987
987
|
this.promptsSubTab = normalized;
|
|
988
|
-
if (
|
|
988
|
+
if (typeof this.loadPromptsTabContent === 'function') this.loadPromptsTabContent();
|
|
989
989
|
this.$nextTick(() => {
|
|
990
990
|
document.querySelector('.main-panel')?.scrollTo({ top: 0, left: 0, behavior: 'auto' });
|
|
991
991
|
});
|
|
992
992
|
},
|
|
993
993
|
|
|
994
|
+
loadPromptsTabContent() {
|
|
995
|
+
if (this.promptsSubTab === 'system') {
|
|
996
|
+
if (typeof this.loadSystemPrompt === 'function') this.loadSystemPrompt();
|
|
997
|
+
} else {
|
|
998
|
+
if (this.promptsSubTab === 'claude-project' && !this.projectPathOptions.length && !this.projectPathOptionsLoading && typeof this.loadProjectPathOptions === 'function') {
|
|
999
|
+
this.loadProjectPathOptions();
|
|
1000
|
+
}
|
|
1001
|
+
if (typeof this.loadPromptsContent === 'function') this.loadPromptsContent();
|
|
1002
|
+
}
|
|
1003
|
+
},
|
|
1004
|
+
|
|
994
1005
|
async loadPromptsContent() {
|
|
995
1006
|
const requestToken = issueLatestRequestToken(this, '_agentsOpenRequestToken');
|
|
996
1007
|
this.agentsLoading = true;
|
|
@@ -22,6 +22,7 @@ import { createOpenclawEditingMethods } from './app.methods.openclaw-editing.mjs
|
|
|
22
22
|
import { createOpenclawPersistMethods } from './app.methods.openclaw-persist.mjs';
|
|
23
23
|
import { createOpencodeConfigMethods } from './app.methods.opencode-config.mjs';
|
|
24
24
|
import { createKilocodeConfigMethods } from './app.methods.kilocode-config.mjs';
|
|
25
|
+
import { createPiConfigMethods } from './app.methods.pi-config.mjs';
|
|
25
26
|
import { createProvidersMethods } from './app.methods.providers.mjs';
|
|
26
27
|
import { createProviderCacheMethods } from './app.methods.provider-cache.mjs';
|
|
27
28
|
import { createRuntimeMethods } from './app.methods.runtime.mjs';
|
|
@@ -102,6 +103,7 @@ export function createAppMethods() {
|
|
|
102
103
|
modelCatalog: OPENCODE_MODEL_CATALOG
|
|
103
104
|
}),
|
|
104
105
|
...createKilocodeConfigMethods({ api }),
|
|
106
|
+
...createPiConfigMethods({ api }),
|
|
105
107
|
...createInstallMethods({ api }),
|
|
106
108
|
...createRuntimeMethods({ api })
|
|
107
109
|
};
|
|
@@ -147,7 +147,7 @@
|
|
|
147
147
|
}
|
|
148
148
|
const resolvedMode = configModeSet.has(normalizedMode) ? normalizedMode : 'codex';
|
|
149
149
|
if (typeof this.isConfigModeVisible === 'function' && !this.isConfigModeVisible(resolvedMode)) {
|
|
150
|
-
const fallback = ['codex', 'claude', 'openclaw', 'opencode', 'kilocode'].find(m => this.isConfigModeVisible(m)) || 'codex';
|
|
150
|
+
const fallback = ['codex', 'claude', 'openclaw', 'opencode', 'kilocode', 'pi'].find(m => this.isConfigModeVisible(m)) || 'codex';
|
|
151
151
|
this.configMode = configModeSet.has(fallback) ? fallback : 'codex';
|
|
152
152
|
} else {
|
|
153
153
|
this.configMode = resolvedMode;
|