codexmate 0.1.4 → 0.1.5
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 +2 -2
- package/README.vi.md +2 -2
- package/README.zh.md +2 -2
- package/cli/analytics-export-args.js +2 -2
- package/cli/session-usage.js +5 -2
- package/cli.js +424 -54
- package/lib/cli-sessions.js +19 -0
- package/package.json +1 -1
- package/web-ui/logic.sessions.mjs +5 -5
- package/web-ui/modules/app.computed.session.mjs +2 -1
- package/web-ui/modules/app.methods.session-actions.mjs +12 -4
- package/web-ui/modules/app.methods.session-browser.mjs +8 -4
- package/web-ui/modules/app.methods.session-trash.mjs +2 -2
- package/web-ui/modules/i18n/locales/en.mjs +1 -0
- package/web-ui/modules/i18n/locales/ja.mjs +1 -0
- package/web-ui/modules/i18n/locales/vi.mjs +1 -0
- package/web-ui/modules/i18n/locales/zh-tw.mjs +1 -0
- package/web-ui/modules/i18n/locales/zh.mjs +1 -0
package/README.md
CHANGED
|
@@ -54,7 +54,7 @@ Have you ever felt overwhelmed by managing multiple local AI agents? Each has it
|
|
|
54
54
|
### What's So Special?
|
|
55
55
|
|
|
56
56
|
Unlike simple wrappers, Codex Mate acts as a **Local Agent Bridge**:
|
|
57
|
-
- **Unified Session Browser**: Search, inspect, filter, and export local sessions across Codex, Claude Code, Gemini CLI,
|
|
57
|
+
- **Unified Session Browser**: Search, inspect, filter, and export local sessions across Codex, Claude Code, Gemini CLI, CodeBuddy Code, and Pi from one place.
|
|
58
58
|
- **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.
|
|
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.
|
|
@@ -72,7 +72,7 @@ Unlike simple wrappers, Codex Mate acts as a **Local Agent Bridge**:
|
|
|
72
72
|
| --- | --- | --- |
|
|
73
73
|
| **Provider Management** | ✅ | Switch providers/models for Codex, Claude, OpenCode, KiloCode, and OpenClaw |
|
|
74
74
|
| **Live Agent Sync** | ✅ | Real-time monitoring of Codex/Claude config & status |
|
|
75
|
-
| **Session Browser** | ✅ | Search, preview, filter, and export sessions across Codex, Claude Code, Gemini CLI,
|
|
75
|
+
| **Session Browser** | ✅ | Search, preview, filter, and export sessions across Codex, Claude Code, Gemini CLI, CodeBuddy Code, and Pi |
|
|
76
76
|
| **Usage Analytics** | ✅ | Visualize message trends and top projects |
|
|
77
77
|
| **Local Skills Market** | ✅ | Cross-app import/export of agent skills |
|
|
78
78
|
| **Task Queue** | ✅ | DAG-based task execution and logs |
|
package/README.vi.md
CHANGED
|
@@ -46,7 +46,7 @@ Bạn có bao giờ cảm thấy rối khi phải quản lý nhiều AI agent c
|
|
|
46
46
|
### Điểm nổi bật
|
|
47
47
|
|
|
48
48
|
Khác với các wrapper đơn giản, Codex Mate hoạt động như một **Agent Bridge cục bộ**:
|
|
49
|
-
- **Trình duyệt phiên thống nhất**: Tìm kiếm và xuất phiên làm việc từ
|
|
49
|
+
- **Trình duyệt phiên thống nhất**: Tìm kiếm, xem trước, lọc và xuất phiên làm việc từ Codex, Claude Code, Gemini CLI, CodeBuddy Code và Pi trong một nơi duy nhất.
|
|
50
50
|
- **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.
|
|
51
51
|
- **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ệ.
|
|
52
52
|
- **Chợ skill cục bộ**: Chia sẻ và nhập skill giữa các app agent khác nhau.
|
|
@@ -60,7 +60,7 @@ Khác với các wrapper đơn giản, Codex Mate hoạt động như một **Ag
|
|
|
60
60
|
| --- | --- | --- |
|
|
61
61
|
| **Quản lý Provider** | ✅ | Chuyển đổi provider/model cho Codex, Claude và OpenClaw |
|
|
62
62
|
| **Đồ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
|
-
| **Trình duyệt phiên** | ✅ | Liệt kê, lọc và xuất phiên (Codex/Claude/Gemini) |
|
|
63
|
+
| **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
64
|
| **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 |
|
|
65
65
|
| **Chợ skill cục bộ** | ✅ | Import/export skill giữa các app agent |
|
|
66
66
|
| **Hàng đợi tác vụ** | ✅ | Thực thi tác vụ theo DAG và xem log |
|
package/README.zh.md
CHANGED
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
### 有什么独特之处?
|
|
54
54
|
|
|
55
55
|
不同于简单的封装,Codex Mate 充当了 **本地智能体桥接器**:
|
|
56
|
-
- **统一会话浏览器**:在一个地方检索、预览、筛选并导出 Codex、Claude Code、Gemini CLI 与
|
|
56
|
+
- **统一会话浏览器**:在一个地方检索、预览、筛选并导出 Codex、Claude Code、Gemini CLI、CodeBuddy Code 与 Pi 的本地会话。
|
|
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 原生配置,避免污染或误删用户已有配置。
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
| --- | --- | --- |
|
|
71
71
|
| **Provider 管理** | ✅ | 切换 Codex、Claude、OpenCode 和 OpenClaw 的 provider/model |
|
|
72
72
|
| **状态实时同步** | ✅ | 实时感知 Codex/Claude 的配置与运行状态变更 |
|
|
73
|
-
| **会话浏览器** | ✅ | 跨 Codex、Claude Code、Gemini CLI 与
|
|
73
|
+
| **会话浏览器** | ✅ | 跨 Codex、Claude Code、Gemini CLI、CodeBuddy Code 与 Pi 的本地会话进行检索、预览、筛选与导出 |
|
|
74
74
|
| **Usage 统计** | ✅ | 可视化消息趋势与热门项目统计 |
|
|
75
75
|
| **本地 Skills 市场** | ✅ | 跨应用的智能体 Skills 导入与导出 |
|
|
76
76
|
| **任务队列** | ✅ | 基于 DAG 的任务执行与日志查看 |
|
|
@@ -54,8 +54,8 @@ function parseAnalyticsExportArgs(args = []) {
|
|
|
54
54
|
if (options.format !== 'csv' && options.format !== 'json') {
|
|
55
55
|
errors.push('--format 必须是 csv 或 json');
|
|
56
56
|
}
|
|
57
|
-
if (options.source && !['codex', 'claude', 'gemini', 'codebuddy', 'all'].includes(options.source)) {
|
|
58
|
-
errors.push('--source 必须是 codex、claude、gemini、codebuddy 或 all');
|
|
57
|
+
if (options.source && !['codex', 'claude', 'gemini', 'codebuddy', 'pi', 'all'].includes(options.source)) {
|
|
58
|
+
errors.push('--source 必须是 codex、claude、gemini、codebuddy、pi 或 all');
|
|
59
59
|
}
|
|
60
60
|
return {
|
|
61
61
|
options,
|
package/cli/session-usage.js
CHANGED
|
@@ -9,11 +9,12 @@ async function listSessionUsageCore(params = {}, deps = {}) {
|
|
|
9
9
|
parseClaudeSessionSummary,
|
|
10
10
|
parseCodeBuddySessionSummary,
|
|
11
11
|
parseGeminiSessionSummary,
|
|
12
|
+
parsePiSessionSummary,
|
|
12
13
|
MAX_SESSION_USAGE_LIST_SIZE,
|
|
13
14
|
SESSION_BROWSE_SUMMARY_READ_BYTES
|
|
14
15
|
} = deps;
|
|
15
16
|
|
|
16
|
-
const source = params.source === 'codex' || params.source === 'claude' || params.source === 'gemini' || params.source === 'codebuddy'
|
|
17
|
+
const source = params.source === 'codex' || params.source === 'claude' || params.source === 'gemini' || params.source === 'codebuddy' || params.source === 'pi'
|
|
17
18
|
? params.source
|
|
18
19
|
: 'all';
|
|
19
20
|
const rawLimit = Number(params.limit);
|
|
@@ -87,7 +88,9 @@ async function listSessionUsageCore(params = {}, deps = {}) {
|
|
|
87
88
|
? parseGeminiSessionSummary(filePath, summaryOptions)
|
|
88
89
|
: (normalized.source === 'codebuddy'
|
|
89
90
|
? parseCodeBuddySessionSummary(filePath, summaryOptions)
|
|
90
|
-
:
|
|
91
|
+
: (normalized.source === 'pi'
|
|
92
|
+
? parsePiSessionSummary(filePath, summaryOptions)
|
|
93
|
+
: parseCodexSessionSummary(filePath, summaryOptions))));
|
|
91
94
|
} catch (_) {
|
|
92
95
|
summary = null;
|
|
93
96
|
}
|
package/cli.js
CHANGED
|
@@ -260,6 +260,8 @@ const CODEXMATE_DERIVED_CODEX_DIR = path.join(CODEXMATE_DERIVED_SESSIONS_DIR, 'c
|
|
|
260
260
|
const CODEXMATE_DERIVED_CLAUDE_DIR = path.join(CODEXMATE_DERIVED_SESSIONS_DIR, 'claude');
|
|
261
261
|
const GEMINI_DIR = path.join(os.homedir(), '.gemini');
|
|
262
262
|
const GEMINI_TMP_DIR = path.join(GEMINI_DIR, 'tmp');
|
|
263
|
+
const PI_DIR = path.join(os.homedir(), '.pi');
|
|
264
|
+
const PI_SESSIONS_DIR = path.join(PI_DIR, 'agent', 'sessions');
|
|
263
265
|
const RECENT_CONFIGS_FILE = path.join(CONFIG_DIR, 'recent-configs.json');
|
|
264
266
|
const WORKFLOW_DEFINITIONS_FILE = path.join(CONFIG_DIR, 'codexmate-workflows.json');
|
|
265
267
|
const WORKFLOW_RUNS_FILE = path.join(CONFIG_DIR, 'codexmate-workflow-runs.jsonl');
|
|
@@ -770,7 +772,8 @@ let g_sessionFileLookupCache = {
|
|
|
770
772
|
codex: new Map(),
|
|
771
773
|
claude: new Map(),
|
|
772
774
|
gemini: new Map(),
|
|
773
|
-
codebuddy: new Map()
|
|
775
|
+
codebuddy: new Map(),
|
|
776
|
+
pi: new Map()
|
|
774
777
|
};
|
|
775
778
|
let g_exactMessageCountCache = new Map();
|
|
776
779
|
let g_modelsCache = new Map();
|
|
@@ -1017,7 +1020,7 @@ function normalizeSidebarCollapsedPreference(value) {
|
|
|
1017
1020
|
|
|
1018
1021
|
function normalizeSessionFilterSourcePreference(value) {
|
|
1019
1022
|
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
1020
|
-
if (normalized === 'all' || normalized === 'claude' || normalized === 'gemini' || normalized === 'codebuddy') return normalized;
|
|
1023
|
+
if (normalized === 'all' || normalized === 'claude' || normalized === 'gemini' || normalized === 'codebuddy' || normalized === 'pi') return normalized;
|
|
1021
1024
|
return 'codex';
|
|
1022
1025
|
}
|
|
1023
1026
|
|
|
@@ -1823,6 +1826,20 @@ function getCodeBuddyProjectsDir() {
|
|
|
1823
1826
|
return resolveExistingDir(candidates, CODEBUDDY_PROJECTS_DIR);
|
|
1824
1827
|
}
|
|
1825
1828
|
|
|
1829
|
+
function getPiSessionsDir() {
|
|
1830
|
+
const candidates = [];
|
|
1831
|
+
const envPiHome = process.env.PI_HOME;
|
|
1832
|
+
if (envPiHome) {
|
|
1833
|
+
candidates.push(path.join(envPiHome, 'agent', 'sessions'));
|
|
1834
|
+
}
|
|
1835
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME;
|
|
1836
|
+
if (xdgConfig) {
|
|
1837
|
+
candidates.push(path.join(xdgConfig, 'pi', 'agent', 'sessions'));
|
|
1838
|
+
}
|
|
1839
|
+
candidates.push(PI_SESSIONS_DIR);
|
|
1840
|
+
return resolveExistingDir(candidates, PI_SESSIONS_DIR);
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1826
1843
|
function getCodexmateDerivedSessionsRoot(target) {
|
|
1827
1844
|
if (target === 'claude') {
|
|
1828
1845
|
return CODEXMATE_DERIVED_CLAUDE_DIR;
|
|
@@ -3842,6 +3859,15 @@ async function countConversationMessagesInFile(filePath, source) {
|
|
|
3842
3859
|
role = '';
|
|
3843
3860
|
}
|
|
3844
3861
|
}
|
|
3862
|
+
} else if (source === 'pi') {
|
|
3863
|
+
if (record && record.type === 'message' && record.message && typeof record.message === 'object') {
|
|
3864
|
+
role = normalizeRole(record.message.role);
|
|
3865
|
+
if (role === 'assistant' || role === 'user' || role === 'system') {
|
|
3866
|
+
text = extractMessageText(record.message.content);
|
|
3867
|
+
} else {
|
|
3868
|
+
role = '';
|
|
3869
|
+
}
|
|
3870
|
+
}
|
|
3845
3871
|
} else {
|
|
3846
3872
|
role = normalizeRole(record.type);
|
|
3847
3873
|
if (role === 'assistant' || role === 'user' || role === 'system') {
|
|
@@ -4010,7 +4036,9 @@ async function hydrateSessionTrashEntries(entries, options = {}) {
|
|
|
4010
4036
|
? 'codex'
|
|
4011
4037
|
: (options.source === 'gemini'
|
|
4012
4038
|
? 'gemini'
|
|
4013
|
-
: (options.source === 'codebuddy'
|
|
4039
|
+
: (options.source === 'codebuddy'
|
|
4040
|
+
? 'codebuddy'
|
|
4041
|
+
: (options.source === 'pi' ? 'pi' : 'all'))));
|
|
4014
4042
|
const hydratedEntries = await mapWithConcurrency(Array.isArray(entries) ? entries : [], 8, async (entry) => {
|
|
4015
4043
|
const normalizedEntry = normalizeSessionTrashEntry(entry);
|
|
4016
4044
|
if (!normalizedEntry) {
|
|
@@ -4019,7 +4047,7 @@ async function hydrateSessionTrashEntries(entries, options = {}) {
|
|
|
4019
4047
|
return await resolveSessionTrashEntryExactMessageCount(normalizedEntry);
|
|
4020
4048
|
});
|
|
4021
4049
|
|
|
4022
|
-
if (source === 'codex' || source === 'claude' || source === 'gemini' || source === 'codebuddy') {
|
|
4050
|
+
if (source === 'codex' || source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'pi') {
|
|
4023
4051
|
return hydratedEntries.filter((entry) => entry.source === source);
|
|
4024
4052
|
}
|
|
4025
4053
|
return hydratedEntries;
|
|
@@ -4037,7 +4065,11 @@ async function hydrateSessionItemsExactMessageCount(items) {
|
|
|
4037
4065
|
? 'claude'
|
|
4038
4066
|
: (item.source === 'codex'
|
|
4039
4067
|
? 'codex'
|
|
4040
|
-
: (item.source === 'gemini'
|
|
4068
|
+
: (item.source === 'gemini'
|
|
4069
|
+
? 'gemini'
|
|
4070
|
+
: (item.source === 'codebuddy'
|
|
4071
|
+
? 'codebuddy'
|
|
4072
|
+
: (item.source === 'pi' ? 'pi' : ''))));
|
|
4041
4073
|
const filePath = typeof item.filePath === 'string' ? item.filePath : '';
|
|
4042
4074
|
if (!source || !filePath || !fs.existsSync(filePath)) {
|
|
4043
4075
|
return item;
|
|
@@ -4085,7 +4117,11 @@ async function readSessionMessageCounts(params = {}) {
|
|
|
4085
4117
|
? 'claude'
|
|
4086
4118
|
: (item.source === 'codex'
|
|
4087
4119
|
? 'codex'
|
|
4088
|
-
: (item.source === 'gemini'
|
|
4120
|
+
: (item.source === 'gemini'
|
|
4121
|
+
? 'gemini'
|
|
4122
|
+
: (item.source === 'codebuddy'
|
|
4123
|
+
? 'codebuddy'
|
|
4124
|
+
: (item.source === 'pi' ? 'pi' : ''))));
|
|
4089
4125
|
const filePath = typeof item.filePath === 'string' ? item.filePath : '';
|
|
4090
4126
|
if (!source || !filePath || !fs.existsSync(filePath)) {
|
|
4091
4127
|
return { key };
|
|
@@ -4599,7 +4635,13 @@ function setSessionListCache(cacheKey, value) {
|
|
|
4599
4635
|
}
|
|
4600
4636
|
|
|
4601
4637
|
function buildSessionInventoryCacheKey(source, limit, options = {}) {
|
|
4602
|
-
const normalizedSource = source === 'claude'
|
|
4638
|
+
const normalizedSource = source === 'claude'
|
|
4639
|
+
? 'claude'
|
|
4640
|
+
: (source === 'gemini'
|
|
4641
|
+
? 'gemini'
|
|
4642
|
+
: (source === 'codebuddy'
|
|
4643
|
+
? 'codebuddy'
|
|
4644
|
+
: (source === 'pi' ? 'pi' : 'codex')));
|
|
4603
4645
|
const normalizedLimit = Number.isFinite(Number(limit))
|
|
4604
4646
|
? Math.max(1, Math.floor(Number(limit)))
|
|
4605
4647
|
: 1;
|
|
@@ -4685,7 +4727,7 @@ function getSessionInventoryCache(cacheKey, forceRefresh = false) {
|
|
|
4685
4727
|
}
|
|
4686
4728
|
|
|
4687
4729
|
function registerSessionFileLookupEntries(source, sessions = []) {
|
|
4688
|
-
const normalizedSource = source === 'claude' || source === 'gemini' || source === 'codebuddy'
|
|
4730
|
+
const normalizedSource = source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'pi'
|
|
4689
4731
|
? source
|
|
4690
4732
|
: 'codex';
|
|
4691
4733
|
const store = g_sessionFileLookupCache[normalizedSource];
|
|
@@ -4726,7 +4768,7 @@ function setSessionInventoryCache(cacheKey, source, value) {
|
|
|
4726
4768
|
}
|
|
4727
4769
|
|
|
4728
4770
|
function listSessionInventoryBySource(source, limit, scanOptions = {}, options = {}) {
|
|
4729
|
-
const normalizedSource = source === 'claude' || source === 'gemini' || source === 'codebuddy'
|
|
4771
|
+
const normalizedSource = source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'pi'
|
|
4730
4772
|
? source
|
|
4731
4773
|
: 'codex';
|
|
4732
4774
|
const forceRefresh = !!options.forceRefresh;
|
|
@@ -4742,7 +4784,9 @@ function listSessionInventoryBySource(source, limit, scanOptions = {}, options =
|
|
|
4742
4784
|
? listGeminiSessions(limit, scanOptions)
|
|
4743
4785
|
: (normalizedSource === 'codebuddy'
|
|
4744
4786
|
? listCodeBuddySessions(limit, scanOptions)
|
|
4745
|
-
:
|
|
4787
|
+
: (normalizedSource === 'pi'
|
|
4788
|
+
? listPiSessions(limit, scanOptions)
|
|
4789
|
+
: listCodexSessions(limit, scanOptions))));
|
|
4746
4790
|
setSessionInventoryCache(cacheKey, normalizedSource, sessions);
|
|
4747
4791
|
return sessions;
|
|
4748
4792
|
}
|
|
@@ -4754,7 +4798,8 @@ function invalidateSessionListCache() {
|
|
|
4754
4798
|
codex: new Map(),
|
|
4755
4799
|
claude: new Map(),
|
|
4756
4800
|
gemini: new Map(),
|
|
4757
|
-
codebuddy: new Map()
|
|
4801
|
+
codebuddy: new Map(),
|
|
4802
|
+
pi: new Map()
|
|
4758
4803
|
};
|
|
4759
4804
|
}
|
|
4760
4805
|
|
|
@@ -4988,7 +5033,13 @@ function readSessionProviderFromRecord(record, source = '') {
|
|
|
4988
5033
|
if (provider) {
|
|
4989
5034
|
return provider;
|
|
4990
5035
|
}
|
|
4991
|
-
return source === 'claude'
|
|
5036
|
+
return source === 'claude'
|
|
5037
|
+
? 'claude'
|
|
5038
|
+
: (source === 'gemini'
|
|
5039
|
+
? 'gemini'
|
|
5040
|
+
: (source === 'codebuddy'
|
|
5041
|
+
? 'codebuddy'
|
|
5042
|
+
: (source === 'pi' ? 'pi' : 'codex')));
|
|
4992
5043
|
}
|
|
4993
5044
|
|
|
4994
5045
|
function applySessionUsageSummaryFromRecord(state, record, source) {
|
|
@@ -5687,6 +5738,177 @@ function parseGeminiSessionSummary(filePath, options = {}) {
|
|
|
5687
5738
|
};
|
|
5688
5739
|
}
|
|
5689
5740
|
|
|
5741
|
+
function extractPiUsageFromMessage(message) {
|
|
5742
|
+
if (!message || typeof message !== 'object' || Array.isArray(message)) {
|
|
5743
|
+
return null;
|
|
5744
|
+
}
|
|
5745
|
+
const usage = message.usage;
|
|
5746
|
+
if (!usage || typeof usage !== 'object' || Array.isArray(usage)) {
|
|
5747
|
+
return null;
|
|
5748
|
+
}
|
|
5749
|
+
const inputTokens = readNonNegativeInteger(usage.input);
|
|
5750
|
+
const outputTokens = readNonNegativeInteger(usage.output);
|
|
5751
|
+
const cachedInputTokens = readNonNegativeInteger(usage.cacheRead);
|
|
5752
|
+
const cacheCreationInputTokens = readNonNegativeInteger(usage.cacheWrite);
|
|
5753
|
+
const reasoningOutputTokens = readNonNegativeInteger(usage.reasoning);
|
|
5754
|
+
if (inputTokens === null && outputTokens === null && cachedInputTokens === null && cacheCreationInputTokens === null && reasoningOutputTokens === null) {
|
|
5755
|
+
return null;
|
|
5756
|
+
}
|
|
5757
|
+
return {
|
|
5758
|
+
inputTokens: inputTokens || 0,
|
|
5759
|
+
cachedInputTokens: cachedInputTokens || 0,
|
|
5760
|
+
cacheCreationInputTokens: cacheCreationInputTokens || 0,
|
|
5761
|
+
outputTokens: outputTokens || 0,
|
|
5762
|
+
reasoningOutputTokens: reasoningOutputTokens || 0
|
|
5763
|
+
};
|
|
5764
|
+
}
|
|
5765
|
+
|
|
5766
|
+
function parsePiSessionSummary(filePath, options = {}) {
|
|
5767
|
+
const summaryReadBytes = Number.isFinite(Number(options.summaryReadBytes))
|
|
5768
|
+
? Math.max(1024, Math.floor(Number(options.summaryReadBytes)))
|
|
5769
|
+
: SESSION_SUMMARY_READ_BYTES;
|
|
5770
|
+
const titleReadBytes = Number.isFinite(Number(options.titleReadBytes))
|
|
5771
|
+
? Math.max(1024, Math.floor(Number(options.titleReadBytes)))
|
|
5772
|
+
: SESSION_TITLE_READ_BYTES;
|
|
5773
|
+
const records = parseJsonlHeadRecords(filePath, summaryReadBytes);
|
|
5774
|
+
if (records.length === 0) {
|
|
5775
|
+
return null;
|
|
5776
|
+
}
|
|
5777
|
+
|
|
5778
|
+
let stat;
|
|
5779
|
+
try {
|
|
5780
|
+
stat = fs.statSync(filePath);
|
|
5781
|
+
} catch (_) {
|
|
5782
|
+
return null;
|
|
5783
|
+
}
|
|
5784
|
+
|
|
5785
|
+
let sessionId = path.basename(filePath, '.jsonl');
|
|
5786
|
+
let cwd = '';
|
|
5787
|
+
let createdAt = '';
|
|
5788
|
+
let updatedAt = stat.mtime.toISOString();
|
|
5789
|
+
let firstPrompt = '';
|
|
5790
|
+
let messageCount = 0;
|
|
5791
|
+
let totalTokens = 0;
|
|
5792
|
+
let contextWindow = 0;
|
|
5793
|
+
let inputTokens = 0;
|
|
5794
|
+
let cachedInputTokens = 0;
|
|
5795
|
+
let cacheCreationInputTokens = 0;
|
|
5796
|
+
let outputTokens = 0;
|
|
5797
|
+
let reasoningOutputTokens = 0;
|
|
5798
|
+
let provider = 'pi';
|
|
5799
|
+
let model = '';
|
|
5800
|
+
const models = [];
|
|
5801
|
+
const previewMessages = [];
|
|
5802
|
+
|
|
5803
|
+
for (const record of records) {
|
|
5804
|
+
if (!createdAt && record && record.type === 'session' && record.timestamp) {
|
|
5805
|
+
createdAt = toIsoTime(record.timestamp, createdAt);
|
|
5806
|
+
}
|
|
5807
|
+
if (record && record.timestamp) {
|
|
5808
|
+
updatedAt = updateLatestIso(updatedAt, record.timestamp);
|
|
5809
|
+
}
|
|
5810
|
+
if (record && record.type === 'session') {
|
|
5811
|
+
sessionId = record.id || sessionId;
|
|
5812
|
+
cwd = record.cwd || cwd;
|
|
5813
|
+
}
|
|
5814
|
+
if (record && record.type === 'model_change' && record.modelId) {
|
|
5815
|
+
if (!models.includes(record.modelId)) {
|
|
5816
|
+
models.push(record.modelId);
|
|
5817
|
+
}
|
|
5818
|
+
model = model || record.modelId;
|
|
5819
|
+
if (record.provider) {
|
|
5820
|
+
provider = record.provider;
|
|
5821
|
+
}
|
|
5822
|
+
}
|
|
5823
|
+
if (record && record.type === 'message' && record.message && typeof record.message === 'object') {
|
|
5824
|
+
const role = normalizeRole(record.message.role);
|
|
5825
|
+
if (role === 'user' || role === 'assistant' || role === 'system') {
|
|
5826
|
+
messageCount += 1;
|
|
5827
|
+
const text = extractMessageText(record.message.content);
|
|
5828
|
+
if (role === 'assistant' && record.message.model && !models.includes(record.message.model)) {
|
|
5829
|
+
models.push(record.message.model);
|
|
5830
|
+
model = model || record.message.model;
|
|
5831
|
+
}
|
|
5832
|
+
if (record.message.provider) {
|
|
5833
|
+
provider = record.message.provider;
|
|
5834
|
+
}
|
|
5835
|
+
const piUsage = extractPiUsageFromMessage(record.message);
|
|
5836
|
+
if (piUsage) {
|
|
5837
|
+
inputTokens += piUsage.inputTokens;
|
|
5838
|
+
cachedInputTokens += piUsage.cachedInputTokens;
|
|
5839
|
+
cacheCreationInputTokens += piUsage.cacheCreationInputTokens;
|
|
5840
|
+
outputTokens += piUsage.outputTokens;
|
|
5841
|
+
reasoningOutputTokens += piUsage.reasoningOutputTokens;
|
|
5842
|
+
}
|
|
5843
|
+
if (text) {
|
|
5844
|
+
previewMessages.push({ role, text });
|
|
5845
|
+
if (!firstPrompt && role === 'user') {
|
|
5846
|
+
firstPrompt = text.split('\n')[0].slice(0, 80);
|
|
5847
|
+
}
|
|
5848
|
+
}
|
|
5849
|
+
}
|
|
5850
|
+
}
|
|
5851
|
+
}
|
|
5852
|
+
|
|
5853
|
+
totalTokens = inputTokens + cachedInputTokens + cacheCreationInputTokens + outputTokens + reasoningOutputTokens;
|
|
5854
|
+
|
|
5855
|
+
const tailRecords = parseJsonlTailRecords(filePath, summaryReadBytes);
|
|
5856
|
+
let tailMessageCount = 0;
|
|
5857
|
+
let tailHasMore = false;
|
|
5858
|
+
for (const record of tailRecords) {
|
|
5859
|
+
if (record && record.timestamp) {
|
|
5860
|
+
updatedAt = updateLatestIso(updatedAt, record.timestamp);
|
|
5861
|
+
}
|
|
5862
|
+
if (record && record.type === 'message' && record.message && typeof record.message === 'object') {
|
|
5863
|
+
const role = normalizeRole(record.message.role);
|
|
5864
|
+
if (role === 'user' || role === 'assistant' || role === 'system') {
|
|
5865
|
+
tailMessageCount += 1;
|
|
5866
|
+
const piUsage = extractPiUsageFromMessage(record.message);
|
|
5867
|
+
if (piUsage) {
|
|
5868
|
+
inputTokens += piUsage.inputTokens;
|
|
5869
|
+
cachedInputTokens += piUsage.cachedInputTokens;
|
|
5870
|
+
cacheCreationInputTokens += piUsage.cacheCreationInputTokens;
|
|
5871
|
+
outputTokens += piUsage.outputTokens;
|
|
5872
|
+
reasoningOutputTokens += piUsage.reasoningOutputTokens;
|
|
5873
|
+
}
|
|
5874
|
+
if (role === 'assistant' && record.message.model && !models.includes(record.message.model)) {
|
|
5875
|
+
models.push(record.message.model);
|
|
5876
|
+
}
|
|
5877
|
+
if (record.message.provider) {
|
|
5878
|
+
provider = record.message.provider;
|
|
5879
|
+
}
|
|
5880
|
+
}
|
|
5881
|
+
}
|
|
5882
|
+
}
|
|
5883
|
+
totalTokens = inputTokens + cachedInputTokens + cacheCreationInputTokens + outputTokens + reasoningOutputTokens;
|
|
5884
|
+
|
|
5885
|
+
const fileName = path.basename(filePath, '.jsonl');
|
|
5886
|
+
return {
|
|
5887
|
+
source: 'pi',
|
|
5888
|
+
sourceLabel: 'Pi',
|
|
5889
|
+
provider,
|
|
5890
|
+
model,
|
|
5891
|
+
models,
|
|
5892
|
+
sessionId,
|
|
5893
|
+
title: firstPrompt || sessionId || fileName,
|
|
5894
|
+
cwd,
|
|
5895
|
+
createdAt,
|
|
5896
|
+
updatedAt,
|
|
5897
|
+
messageCount,
|
|
5898
|
+
totalTokens,
|
|
5899
|
+
contextWindow,
|
|
5900
|
+
inputTokens,
|
|
5901
|
+
cachedInputTokens,
|
|
5902
|
+
cacheCreationInputTokens,
|
|
5903
|
+
outputTokens,
|
|
5904
|
+
reasoningOutputTokens,
|
|
5905
|
+
__messageCountExact: false,
|
|
5906
|
+
filePath,
|
|
5907
|
+
keywords: [],
|
|
5908
|
+
capabilities: { code: true }
|
|
5909
|
+
};
|
|
5910
|
+
}
|
|
5911
|
+
|
|
5690
5912
|
function listCodexSessions(limit, options = {}) {
|
|
5691
5913
|
const codexSessionsDir = getCodexSessionsDir();
|
|
5692
5914
|
const scanFactor = Number.isFinite(Number(options.scanFactor))
|
|
@@ -6126,8 +6348,56 @@ function listCodeBuddySessions(limit, options = {}) {
|
|
|
6126
6348
|
return mergeAndLimitSessions(sessions, limit);
|
|
6127
6349
|
}
|
|
6128
6350
|
|
|
6351
|
+
function listPiSessions(limit, options = {}) {
|
|
6352
|
+
const sessionsDir = getPiSessionsDir();
|
|
6353
|
+
if (!fs.existsSync(sessionsDir)) {
|
|
6354
|
+
return [];
|
|
6355
|
+
}
|
|
6356
|
+
|
|
6357
|
+
const scanFactor = Number.isFinite(Number(options.scanFactor))
|
|
6358
|
+
? Math.max(1, Number(options.scanFactor))
|
|
6359
|
+
: SESSION_SCAN_FACTOR;
|
|
6360
|
+
const minFiles = Number.isFinite(Number(options.minFiles))
|
|
6361
|
+
? Math.max(1, Number(options.minFiles))
|
|
6362
|
+
: Math.min(SESSION_SCAN_MIN_FILES, MAX_SESSION_LIST_SIZE * SESSION_SCAN_FACTOR);
|
|
6363
|
+
const targetCount = Number.isFinite(Number(options.targetCount))
|
|
6364
|
+
? Math.max(1, Math.floor(Number(options.targetCount)))
|
|
6365
|
+
: Math.max(1, Math.floor(limit * scanFactor));
|
|
6366
|
+
const scanCount = Number.isFinite(Number(options.scanCount))
|
|
6367
|
+
? Math.max(targetCount, Math.floor(Number(options.scanCount)))
|
|
6368
|
+
: Math.max(targetCount, minFiles);
|
|
6369
|
+
const maxFilesScanned = Number.isFinite(Number(options.maxFilesScanned))
|
|
6370
|
+
? Math.max(scanCount, Math.floor(Number(options.maxFilesScanned)))
|
|
6371
|
+
: Math.max(scanCount * 2, minFiles);
|
|
6372
|
+
const summaryReadBytes = Number.isFinite(Number(options.summaryReadBytes))
|
|
6373
|
+
? Math.max(1024, Math.floor(Number(options.summaryReadBytes)))
|
|
6374
|
+
: SESSION_SUMMARY_READ_BYTES;
|
|
6375
|
+
const titleReadBytes = Number.isFinite(Number(options.titleReadBytes))
|
|
6376
|
+
? Math.max(1024, Math.floor(Number(options.titleReadBytes)))
|
|
6377
|
+
: SESSION_TITLE_READ_BYTES;
|
|
6378
|
+
|
|
6379
|
+
const files = collectRecentJsonlFiles(sessionsDir, {
|
|
6380
|
+
returnCount: scanCount,
|
|
6381
|
+
maxFilesScanned
|
|
6382
|
+
});
|
|
6383
|
+
const sessions = [];
|
|
6384
|
+
for (const filePath of files) {
|
|
6385
|
+
const summary = parsePiSessionSummary(filePath, {
|
|
6386
|
+
summaryReadBytes,
|
|
6387
|
+
titleReadBytes
|
|
6388
|
+
});
|
|
6389
|
+
if (summary) {
|
|
6390
|
+
sessions.push(summary);
|
|
6391
|
+
}
|
|
6392
|
+
if (sessions.length >= targetCount) {
|
|
6393
|
+
break;
|
|
6394
|
+
}
|
|
6395
|
+
}
|
|
6396
|
+
return mergeAndLimitSessions(sessions, limit);
|
|
6397
|
+
}
|
|
6398
|
+
|
|
6129
6399
|
async function listAllSessions(params = {}) {
|
|
6130
|
-
const source = params.source === 'codex' || params.source === 'claude' || params.source === 'gemini' || params.source === 'codebuddy'
|
|
6400
|
+
const source = params.source === 'codex' || params.source === 'claude' || params.source === 'gemini' || params.source === 'codebuddy' || params.source === 'pi'
|
|
6131
6401
|
? params.source
|
|
6132
6402
|
: 'all';
|
|
6133
6403
|
const rawLimit = Number(params.limit);
|
|
@@ -6177,6 +6447,9 @@ async function listAllSessions(params = {}) {
|
|
|
6177
6447
|
if (source === 'all' || source === 'codebuddy') {
|
|
6178
6448
|
sessions = sessions.concat(listSessionInventoryBySource('codebuddy', limit, scanOptions, { forceRefresh }));
|
|
6179
6449
|
}
|
|
6450
|
+
if (source === 'all' || source === 'pi') {
|
|
6451
|
+
sessions = sessions.concat(listSessionInventoryBySource('pi', limit, scanOptions, { forceRefresh }));
|
|
6452
|
+
}
|
|
6180
6453
|
|
|
6181
6454
|
if (hasPathFilter) {
|
|
6182
6455
|
sessions = sessions.filter(item => matchesSessionPathFilter(item, normalizedPathFilter));
|
|
@@ -6259,6 +6532,7 @@ async function listSessionUsage(params = {}) {
|
|
|
6259
6532
|
parseClaudeSessionSummary,
|
|
6260
6533
|
parseCodeBuddySessionSummary,
|
|
6261
6534
|
parseGeminiSessionSummary,
|
|
6535
|
+
parsePiSessionSummary,
|
|
6262
6536
|
MAX_SESSION_USAGE_LIST_SIZE,
|
|
6263
6537
|
SESSION_BROWSE_SUMMARY_READ_BYTES
|
|
6264
6538
|
});
|
|
@@ -6272,10 +6546,10 @@ async function exportSessionUsage(params = {}) {
|
|
|
6272
6546
|
|
|
6273
6547
|
function listSessionPaths(params = {}) {
|
|
6274
6548
|
const source = typeof params.source === 'string' ? params.source.trim().toLowerCase() : '';
|
|
6275
|
-
if (source && source !== 'codex' && source !== 'claude' && source !== 'gemini' && source !== 'codebuddy' && source !== 'all') {
|
|
6549
|
+
if (source && source !== 'codex' && source !== 'claude' && source !== 'gemini' && source !== 'codebuddy' && source !== 'pi' && source !== 'all') {
|
|
6276
6550
|
return [];
|
|
6277
6551
|
}
|
|
6278
|
-
const validSource = source === 'codex' || source === 'claude' || source === 'gemini' || source === 'codebuddy' ? source : 'all';
|
|
6552
|
+
const validSource = source === 'codex' || source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'pi' ? source : 'all';
|
|
6279
6553
|
const rawLimit = Number(params.limit);
|
|
6280
6554
|
const limit = Number.isFinite(rawLimit)
|
|
6281
6555
|
? Math.max(1, Math.min(rawLimit, MAX_SESSION_PATH_LIST_SIZE))
|
|
@@ -6309,6 +6583,9 @@ function listSessionPaths(params = {}) {
|
|
|
6309
6583
|
if (validSource === 'all' || validSource === 'codebuddy') {
|
|
6310
6584
|
sessions = sessions.concat(listSessionInventoryBySource('codebuddy', gatherLimit, scanOptions, { forceRefresh }));
|
|
6311
6585
|
}
|
|
6586
|
+
if (validSource === 'all' || validSource === 'pi') {
|
|
6587
|
+
sessions = sessions.concat(listSessionInventoryBySource('pi', gatherLimit, scanOptions, { forceRefresh }));
|
|
6588
|
+
}
|
|
6312
6589
|
|
|
6313
6590
|
const dedupedPaths = [];
|
|
6314
6591
|
const seen = new Set();
|
|
@@ -6334,7 +6611,7 @@ function listSessionPaths(params = {}) {
|
|
|
6334
6611
|
}
|
|
6335
6612
|
|
|
6336
6613
|
function resolveSessionFilePath(source, filePath, sessionId) {
|
|
6337
|
-
const normalizedSource = source === 'claude' || source === 'gemini' || source === 'codebuddy'
|
|
6614
|
+
const normalizedSource = source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'pi'
|
|
6338
6615
|
? source
|
|
6339
6616
|
: 'codex';
|
|
6340
6617
|
const homeDir = process && process.env && process.env.HOME ? process.env.HOME : '';
|
|
@@ -6346,7 +6623,9 @@ function resolveSessionFilePath(source, filePath, sessionId) {
|
|
|
6346
6623
|
? [getGeminiTmpDir()]
|
|
6347
6624
|
: (normalizedSource === 'codebuddy'
|
|
6348
6625
|
? [getCodeBuddyProjectsDir()]
|
|
6349
|
-
:
|
|
6626
|
+
: (normalizedSource === 'pi'
|
|
6627
|
+
? [getPiSessionsDir()]
|
|
6628
|
+
: [getCodexSessionsDir(), derivedCodexDir])));
|
|
6350
6629
|
const availableRoots = roots.filter((dirPath) => dirPath && fs.existsSync(dirPath));
|
|
6351
6630
|
if (availableRoots.length === 0) {
|
|
6352
6631
|
return '';
|
|
@@ -6894,13 +7173,21 @@ function buildSessionSummaryFallback(source, filePath, sessionId = '') {
|
|
|
6894
7173
|
const resolvedSessionId = sessionId || path.basename(filePath, '.jsonl');
|
|
6895
7174
|
const sourceLabel = source === 'claude'
|
|
6896
7175
|
? 'Claude Code'
|
|
6897
|
-
: (source === 'gemini'
|
|
7176
|
+
: (source === 'gemini'
|
|
7177
|
+
? 'Gemini CLI'
|
|
7178
|
+
: (source === 'codebuddy'
|
|
7179
|
+
? 'CodeBuddy Code'
|
|
7180
|
+
: (source === 'pi' ? 'Pi' : 'Codex')));
|
|
6898
7181
|
return {
|
|
6899
7182
|
source,
|
|
6900
7183
|
sourceLabel,
|
|
6901
7184
|
provider: source === 'claude'
|
|
6902
7185
|
? 'claude'
|
|
6903
|
-
: (source === 'gemini'
|
|
7186
|
+
: (source === 'gemini'
|
|
7187
|
+
? 'gemini'
|
|
7188
|
+
: (source === 'codebuddy'
|
|
7189
|
+
? 'codebuddy'
|
|
7190
|
+
: (source === 'pi' ? 'pi' : 'codex'))),
|
|
6904
7191
|
sessionId: resolvedSessionId,
|
|
6905
7192
|
title: resolvedSessionId,
|
|
6906
7193
|
cwd: '',
|
|
@@ -6953,7 +7240,9 @@ function normalizeSessionTrashEntry(entry) {
|
|
|
6953
7240
|
? 'codex'
|
|
6954
7241
|
: (entry.source === 'gemini'
|
|
6955
7242
|
? 'gemini'
|
|
6956
|
-
: (entry.source === 'codebuddy'
|
|
7243
|
+
: (entry.source === 'codebuddy'
|
|
7244
|
+
? 'codebuddy'
|
|
7245
|
+
: (entry.source === 'pi' ? 'pi' : ''))));
|
|
6957
7246
|
const trashId = typeof entry.trashId === 'string' ? entry.trashId.trim() : '';
|
|
6958
7247
|
if (!source || !trashId || trashId.includes('/') || trashId.includes('\\') || trashId.includes('\0')) {
|
|
6959
7248
|
return null;
|
|
@@ -6970,7 +7259,11 @@ function normalizeSessionTrashEntry(entry) {
|
|
|
6970
7259
|
source,
|
|
6971
7260
|
sourceLabel: source === 'claude'
|
|
6972
7261
|
? 'Claude Code'
|
|
6973
|
-
: (source === 'gemini'
|
|
7262
|
+
: (source === 'gemini'
|
|
7263
|
+
? 'Gemini CLI'
|
|
7264
|
+
: (source === 'codebuddy'
|
|
7265
|
+
? 'CodeBuddy Code'
|
|
7266
|
+
: (source === 'pi' ? 'Pi' : 'Codex'))),
|
|
6974
7267
|
sessionId: sessionId || trashId,
|
|
6975
7268
|
title: typeof entry.title === 'string' && entry.title.trim() ? entry.title.trim() : (sessionId || trashId),
|
|
6976
7269
|
cwd: typeof entry.cwd === 'string' ? entry.cwd : '',
|
|
@@ -6986,7 +7279,7 @@ function normalizeSessionTrashEntry(entry) {
|
|
|
6986
7279
|
originalFilePath: typeof entry.originalFilePath === 'string' ? entry.originalFilePath : '',
|
|
6987
7280
|
provider: typeof entry.provider === 'string' && entry.provider.trim()
|
|
6988
7281
|
? entry.provider.trim()
|
|
6989
|
-
: (source === 'claude' ? 'claude' : (source === 'gemini' ? 'gemini' : (source === 'codebuddy' ? 'codebuddy' : 'codex'))),
|
|
7282
|
+
: (source === 'claude' ? 'claude' : (source === 'gemini' ? 'gemini' : (source === 'codebuddy' ? 'codebuddy' : (source === 'pi' ? 'pi' : 'codex')))),
|
|
6990
7283
|
keywords: normalizeKeywords(entry.keywords),
|
|
6991
7284
|
capabilities: normalizeCapabilities(entry.capabilities),
|
|
6992
7285
|
claudeIndexPath: typeof entry.claudeIndexPath === 'string' ? entry.claudeIndexPath : '',
|
|
@@ -7073,7 +7366,13 @@ function purgeExpiredSessionTrashEntries(retentionDays) {
|
|
|
7073
7366
|
}
|
|
7074
7367
|
|
|
7075
7368
|
function buildSessionTrashEntry(summary, options = {}) {
|
|
7076
|
-
const source = options.source === 'claude'
|
|
7369
|
+
const source = options.source === 'claude'
|
|
7370
|
+
? 'claude'
|
|
7371
|
+
: (options.source === 'gemini'
|
|
7372
|
+
? 'gemini'
|
|
7373
|
+
: (options.source === 'codebuddy'
|
|
7374
|
+
? 'codebuddy'
|
|
7375
|
+
: (options.source === 'pi' ? 'pi' : 'codex')));
|
|
7077
7376
|
const sessionId = options.sessionId || summary.sessionId || path.basename(options.originalFilePath || summary.filePath || '', '.jsonl');
|
|
7078
7377
|
const claudeIndexEntry = options.claudeIndexEntry && typeof options.claudeIndexEntry === 'object' && !Array.isArray(options.claudeIndexEntry)
|
|
7079
7378
|
? options.claudeIndexEntry
|
|
@@ -7081,7 +7380,13 @@ function buildSessionTrashEntry(summary, options = {}) {
|
|
|
7081
7380
|
const deletedAt = typeof options.deletedAt === 'string' && options.deletedAt
|
|
7082
7381
|
? options.deletedAt
|
|
7083
7382
|
: new Date().toISOString();
|
|
7084
|
-
const sourceLabel = source === 'claude'
|
|
7383
|
+
const sourceLabel = source === 'claude'
|
|
7384
|
+
? 'Claude Code'
|
|
7385
|
+
: (source === 'gemini'
|
|
7386
|
+
? 'Gemini CLI'
|
|
7387
|
+
: (source === 'codebuddy'
|
|
7388
|
+
? 'CodeBuddy Code'
|
|
7389
|
+
: (source === 'pi' ? 'Pi' : 'Codex')));
|
|
7085
7390
|
const fallbackTitle = truncateText(
|
|
7086
7391
|
(claudeIndexEntry && (claudeIndexEntry.summary || claudeIndexEntry.firstPrompt)) || sessionId,
|
|
7087
7392
|
120
|
|
@@ -7118,7 +7423,7 @@ function buildSessionTrashEntry(summary, options = {}) {
|
|
|
7118
7423
|
originalFilePath: options.originalFilePath || summary.filePath || '',
|
|
7119
7424
|
provider: (claudeIndexEntry && typeof claudeIndexEntry.provider === 'string' && claudeIndexEntry.provider.trim())
|
|
7120
7425
|
? claudeIndexEntry.provider.trim()
|
|
7121
|
-
: (summary.provider || (source === 'claude' ? 'claude' : 'codex')),
|
|
7426
|
+
: (summary.provider || (source === 'claude' ? 'claude' : (source === 'gemini' ? 'gemini' : (source === 'codebuddy' ? 'codebuddy' : (source === 'pi' ? 'pi' : 'codex'))))),
|
|
7122
7427
|
keywords: normalizedClaudeKeywords.length > 0 ? normalizedClaudeKeywords : normalizedSummaryKeywords,
|
|
7123
7428
|
capabilities: Object.keys(normalizedClaudeCapabilities).length > 0
|
|
7124
7429
|
? normalizedClaudeCapabilities
|
|
@@ -7137,7 +7442,9 @@ function resolveSessionRestoreTarget(entry) {
|
|
|
7137
7442
|
? getClaudeProjectsDir()
|
|
7138
7443
|
: (normalized.source === 'gemini'
|
|
7139
7444
|
? getGeminiTmpDir()
|
|
7140
|
-
: (normalized.source === 'codebuddy'
|
|
7445
|
+
: (normalized.source === 'codebuddy'
|
|
7446
|
+
? getCodeBuddyProjectsDir()
|
|
7447
|
+
: (normalized.source === 'pi' ? getPiSessionsDir() : getCodexSessionsDir())));
|
|
7141
7448
|
const originalFilePath = typeof normalized.originalFilePath === 'string' ? normalized.originalFilePath.trim() : '';
|
|
7142
7449
|
if (!root || !originalFilePath) {
|
|
7143
7450
|
return '';
|
|
@@ -7277,7 +7584,9 @@ async function listSessionTrashItems(params = {}) {
|
|
|
7277
7584
|
? 'codex'
|
|
7278
7585
|
: (params.source === 'gemini'
|
|
7279
7586
|
? 'gemini'
|
|
7280
|
-
: (params.source === 'codebuddy'
|
|
7587
|
+
: (params.source === 'codebuddy'
|
|
7588
|
+
? 'codebuddy'
|
|
7589
|
+
: (params.source === 'pi' ? 'pi' : 'all'))));
|
|
7281
7590
|
const countOnly = params.countOnly === true;
|
|
7282
7591
|
const rawLimit = Number(params.limit);
|
|
7283
7592
|
const limit = Number.isFinite(rawLimit)
|
|
@@ -7287,7 +7596,7 @@ async function listSessionTrashItems(params = {}) {
|
|
|
7287
7596
|
purgeExpiredSessionTrashEntries(params.retentionDays);
|
|
7288
7597
|
}
|
|
7289
7598
|
const allEntries = readSessionTrashEntries();
|
|
7290
|
-
let items = source === 'codex' || source === 'claude' || source === 'gemini' || source === 'codebuddy'
|
|
7599
|
+
let items = source === 'codex' || source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'pi'
|
|
7291
7600
|
? allEntries.filter((entry) => entry.source === source)
|
|
7292
7601
|
: allEntries.slice();
|
|
7293
7602
|
items.sort((a, b) => {
|
|
@@ -7473,7 +7782,9 @@ async function trashSessionData(params = {}) {
|
|
|
7473
7782
|
? 'codex'
|
|
7474
7783
|
: (params.source === 'gemini'
|
|
7475
7784
|
? 'gemini'
|
|
7476
|
-
: (params.source === 'codebuddy'
|
|
7785
|
+
: (params.source === 'codebuddy'
|
|
7786
|
+
? 'codebuddy'
|
|
7787
|
+
: (params.source === 'pi' ? 'pi' : ''))));
|
|
7477
7788
|
if (!source) {
|
|
7478
7789
|
return { error: 'Invalid source' };
|
|
7479
7790
|
}
|
|
@@ -7487,7 +7798,9 @@ async function trashSessionData(params = {}) {
|
|
|
7487
7798
|
? parseClaudeSessionSummary(filePath)
|
|
7488
7799
|
: (source === 'gemini'
|
|
7489
7800
|
? parseGeminiSessionSummary(filePath)
|
|
7490
|
-
: (source === 'codebuddy'
|
|
7801
|
+
: (source === 'codebuddy'
|
|
7802
|
+
? parseCodeBuddySessionSummary(filePath)
|
|
7803
|
+
: (source === 'pi' ? parsePiSessionSummary(filePath) : parseCodexSessionSummary(filePath)))))
|
|
7491
7804
|
|| buildSessionSummaryFallback(source, filePath, params.sessionId);
|
|
7492
7805
|
const exactMessageCount = await countConversationMessagesInFile(filePath, source);
|
|
7493
7806
|
if (Number.isFinite(Number(exactMessageCount))) {
|
|
@@ -7584,7 +7897,9 @@ async function deleteSessionData(params = {}) {
|
|
|
7584
7897
|
? 'codex'
|
|
7585
7898
|
: (params.source === 'gemini'
|
|
7586
7899
|
? 'gemini'
|
|
7587
|
-
: (params.source === 'codebuddy'
|
|
7900
|
+
: (params.source === 'codebuddy'
|
|
7901
|
+
? 'codebuddy'
|
|
7902
|
+
: (params.source === 'pi' ? 'pi' : ''))));
|
|
7588
7903
|
if (!source) {
|
|
7589
7904
|
return { error: 'Invalid source' };
|
|
7590
7905
|
}
|
|
@@ -8054,6 +8369,32 @@ function extractCodeBuddyMessageFromRecord(record, state, lineIndex = -1) {
|
|
|
8054
8369
|
}
|
|
8055
8370
|
}
|
|
8056
8371
|
|
|
8372
|
+
function extractPiMessageFromRecord(record, state, lineIndex = -1) {
|
|
8373
|
+
if (record && record.timestamp) {
|
|
8374
|
+
state.updatedAt = toIsoTime(record.timestamp, state.updatedAt);
|
|
8375
|
+
}
|
|
8376
|
+
if (record && record.type === 'session') {
|
|
8377
|
+
state.sessionId = record.id || state.sessionId;
|
|
8378
|
+
state.cwd = record.cwd || state.cwd;
|
|
8379
|
+
return;
|
|
8380
|
+
}
|
|
8381
|
+
if (!record || record.type !== 'message' || !record.message || typeof record.message !== 'object') {
|
|
8382
|
+
return;
|
|
8383
|
+
}
|
|
8384
|
+
const role = normalizeRole(record.message.role);
|
|
8385
|
+
if (role === 'user' || role === 'assistant' || role === 'system') {
|
|
8386
|
+
const text = extractMessageText(record.message.content);
|
|
8387
|
+
if (text && canAppendMessage(state)) {
|
|
8388
|
+
state.messages.push({
|
|
8389
|
+
role,
|
|
8390
|
+
text,
|
|
8391
|
+
timestamp: toIsoTime(record.timestamp, ''),
|
|
8392
|
+
recordLineIndex: Number.isInteger(lineIndex) ? lineIndex : -1
|
|
8393
|
+
});
|
|
8394
|
+
}
|
|
8395
|
+
}
|
|
8396
|
+
}
|
|
8397
|
+
|
|
8057
8398
|
function recordHasCodexMessage(record) {
|
|
8058
8399
|
if (!record || record.type !== 'response_item' || !record.payload) {
|
|
8059
8400
|
return false;
|
|
@@ -8095,9 +8436,22 @@ function recordHasCodeBuddyMessage(record) {
|
|
|
8095
8436
|
return !!text;
|
|
8096
8437
|
}
|
|
8097
8438
|
|
|
8439
|
+
function recordHasPiMessage(record) {
|
|
8440
|
+
if (!record || record.type !== 'message' || !record.message || typeof record.message !== 'object') {
|
|
8441
|
+
return false;
|
|
8442
|
+
}
|
|
8443
|
+
const role = normalizeRole(record.message.role);
|
|
8444
|
+
if (role !== 'user' && role !== 'assistant' && role !== 'system') {
|
|
8445
|
+
return false;
|
|
8446
|
+
}
|
|
8447
|
+
const text = extractMessageText(record.message.content);
|
|
8448
|
+
return !!text;
|
|
8449
|
+
}
|
|
8450
|
+
|
|
8098
8451
|
function recordHasMessage(record, source) {
|
|
8099
8452
|
if (source === 'codex') return recordHasCodexMessage(record);
|
|
8100
8453
|
if (source === 'codebuddy') return recordHasCodeBuddyMessage(record);
|
|
8454
|
+
if (source === 'pi') return recordHasPiMessage(record);
|
|
8101
8455
|
return recordHasClaudeMessage(record);
|
|
8102
8456
|
}
|
|
8103
8457
|
|
|
@@ -8118,6 +8472,8 @@ function extractMessagesFromRecords(records, source, options = {}) {
|
|
|
8118
8472
|
extractCodexMessageFromRecord(record, state, lineIndex);
|
|
8119
8473
|
} else if (source === 'codebuddy') {
|
|
8120
8474
|
extractCodeBuddyMessageFromRecord(record, state, lineIndex);
|
|
8475
|
+
} else if (source === 'pi') {
|
|
8476
|
+
extractPiMessageFromRecord(record, state, lineIndex);
|
|
8121
8477
|
} else {
|
|
8122
8478
|
extractClaudeMessageFromRecord(record, state, lineIndex);
|
|
8123
8479
|
}
|
|
@@ -8181,6 +8537,8 @@ async function extractMessagesFromFile(filePath, source, options = {}) {
|
|
|
8181
8537
|
extractCodexMessageFromRecord(record, state, currentLineIndex);
|
|
8182
8538
|
} else if (source === 'codebuddy') {
|
|
8183
8539
|
extractCodeBuddyMessageFromRecord(record, state, currentLineIndex);
|
|
8540
|
+
} else if (source === 'pi') {
|
|
8541
|
+
extractPiMessageFromRecord(record, state, currentLineIndex);
|
|
8184
8542
|
} else {
|
|
8185
8543
|
extractClaudeMessageFromRecord(record, state, currentLineIndex);
|
|
8186
8544
|
}
|
|
@@ -8211,7 +8569,9 @@ async function readSessionDetail(params = {}) {
|
|
|
8211
8569
|
? 'codex'
|
|
8212
8570
|
: (params.source === 'gemini'
|
|
8213
8571
|
? 'gemini'
|
|
8214
|
-
: (params.source === 'codebuddy'
|
|
8572
|
+
: (params.source === 'codebuddy'
|
|
8573
|
+
? 'codebuddy'
|
|
8574
|
+
: (params.source === 'pi' ? 'pi' : ''))));
|
|
8215
8575
|
if (!source) {
|
|
8216
8576
|
return { error: 'Invalid source' };
|
|
8217
8577
|
}
|
|
@@ -8273,7 +8633,9 @@ async function readSessionDetail(params = {}) {
|
|
|
8273
8633
|
? 'Codex'
|
|
8274
8634
|
: (source === 'claude'
|
|
8275
8635
|
? 'Claude Code'
|
|
8276
|
-
: (source === 'gemini'
|
|
8636
|
+
: (source === 'gemini'
|
|
8637
|
+
? 'Gemini CLI'
|
|
8638
|
+
: (source === 'pi' ? 'Pi' : 'CodeBuddy Code')));
|
|
8277
8639
|
const clippedMessages = Array.isArray(extracted.messages) ? extracted.messages : [];
|
|
8278
8640
|
const hasExactTotalMessages = Number.isFinite(extracted.totalMessages);
|
|
8279
8641
|
const startIndex = hasExactTotalMessages
|
|
@@ -8340,7 +8702,9 @@ async function readSessionPlain(params = {}) {
|
|
|
8340
8702
|
? 'codex'
|
|
8341
8703
|
: (params.source === 'gemini'
|
|
8342
8704
|
? 'gemini'
|
|
8343
|
-
: (params.source === 'codebuddy'
|
|
8705
|
+
: (params.source === 'codebuddy'
|
|
8706
|
+
? 'codebuddy'
|
|
8707
|
+
: (params.source === 'pi' ? 'pi' : ''))));
|
|
8344
8708
|
if (!source) {
|
|
8345
8709
|
return { error: 'Invalid source' };
|
|
8346
8710
|
}
|
|
@@ -8414,7 +8778,9 @@ async function readSessionPlain(params = {}) {
|
|
|
8414
8778
|
? 'Codex'
|
|
8415
8779
|
: (source === 'claude'
|
|
8416
8780
|
? 'Claude Code'
|
|
8417
|
-
: (source === 'gemini'
|
|
8781
|
+
: (source === 'gemini'
|
|
8782
|
+
? 'Gemini CLI'
|
|
8783
|
+
: (source === 'pi' ? 'Pi' : 'CodeBuddy Code')));
|
|
8418
8784
|
const messages = removeLeadingSystemMessage(Array.isArray(extracted.messages) ? extracted.messages : []);
|
|
8419
8785
|
const text = buildSessionPlainText(messages);
|
|
8420
8786
|
|
|
@@ -8436,7 +8802,9 @@ async function exportSessionData(params = {}) {
|
|
|
8436
8802
|
? 'codex'
|
|
8437
8803
|
: (params.source === 'gemini'
|
|
8438
8804
|
? 'gemini'
|
|
8439
|
-
: (params.source === 'codebuddy'
|
|
8805
|
+
: (params.source === 'codebuddy'
|
|
8806
|
+
? 'codebuddy'
|
|
8807
|
+
: (params.source === 'pi' ? 'pi' : ''))));
|
|
8440
8808
|
if (!source) {
|
|
8441
8809
|
return { error: 'Invalid source' };
|
|
8442
8810
|
}
|
|
@@ -8510,7 +8878,9 @@ async function exportSessionData(params = {}) {
|
|
|
8510
8878
|
? 'Codex'
|
|
8511
8879
|
: (source === 'claude'
|
|
8512
8880
|
? 'Claude Code'
|
|
8513
|
-
: (source === 'gemini'
|
|
8881
|
+
: (source === 'gemini'
|
|
8882
|
+
? 'Gemini CLI'
|
|
8883
|
+
: (source === 'pi' ? 'Pi' : 'CodeBuddy Code')));
|
|
8514
8884
|
const truncated = !!extracted.truncated;
|
|
8515
8885
|
const maxMessagesLabel = maxMessages === Infinity ? 'all' : maxMessages;
|
|
8516
8886
|
const markdown = buildSessionMarkdown({
|
|
@@ -11696,7 +12066,7 @@ function resolveExportOutputPath(outputPath, defaultFileName) {
|
|
|
11696
12066
|
}
|
|
11697
12067
|
|
|
11698
12068
|
function printExportSessionUsage() {
|
|
11699
|
-
console.log('\n用法: codexmate export-session --source <codex|claude|gemini|codebuddy> (--session-id <ID>|--file <PATH>) [--output <PATH>] [--max-messages <N|all|Infinity>]');
|
|
12069
|
+
console.log('\n用法: codexmate export-session --source <codex|claude|gemini|codebuddy|pi> (--session-id <ID>|--file <PATH>) [--output <PATH>] [--max-messages <N|all|Infinity>]');
|
|
11700
12070
|
console.log('\n示例:');
|
|
11701
12071
|
console.log(' codexmate export-session --source codex --session-id 123456');
|
|
11702
12072
|
console.log(' codexmate export-session --source claude --file "~/.claude/projects/demo/session.jsonl"');
|
|
@@ -11768,8 +12138,8 @@ function parseExportSessionArgs(args = []) {
|
|
|
11768
12138
|
}
|
|
11769
12139
|
|
|
11770
12140
|
const normalizedSource = options.source.trim().toLowerCase();
|
|
11771
|
-
if (normalizedSource && normalizedSource !== 'codex' && normalizedSource !== 'claude') {
|
|
11772
|
-
errors.push('参数 --source 仅支持 codex 或
|
|
12141
|
+
if (normalizedSource && normalizedSource !== 'codex' && normalizedSource !== 'claude' && normalizedSource !== 'pi') {
|
|
12142
|
+
errors.push('参数 --source 仅支持 codex、claude 或 pi');
|
|
11773
12143
|
}
|
|
11774
12144
|
options.source = normalizedSource;
|
|
11775
12145
|
|
|
@@ -11842,7 +12212,7 @@ async function cmdExportSession(args = []) {
|
|
|
11842
12212
|
|
|
11843
12213
|
function printAnalyticsUsage() {
|
|
11844
12214
|
console.log('\n用法:');
|
|
11845
|
-
console.log(' codexmate analytics export [--format csv|json] [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--model <MODEL>] [--source <codex|claude|gemini|codebuddy|all>] [--output <PATH|->] [-o <PATH|->]');
|
|
12215
|
+
console.log(' codexmate analytics export [--format csv|json] [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--model <MODEL>] [--source <codex|claude|gemini|codebuddy|pi|all>] [--output <PATH|->] [-o <PATH|->]');
|
|
11846
12216
|
console.log('');
|
|
11847
12217
|
}
|
|
11848
12218
|
|
|
@@ -13260,8 +13630,8 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
13260
13630
|
case 'list-sessions':
|
|
13261
13631
|
{
|
|
13262
13632
|
const source = typeof params.source === 'string' ? params.source.trim().toLowerCase() : '';
|
|
13263
|
-
if (source && source !== 'codex' && source !== 'claude' && source !== 'gemini' && source !== 'codebuddy' && source !== 'all') {
|
|
13264
|
-
result = { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, or all' };
|
|
13633
|
+
if (source && source !== 'codex' && source !== 'claude' && source !== 'gemini' && source !== 'codebuddy' && source !== 'pi' && source !== 'all') {
|
|
13634
|
+
result = { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, pi, or all' };
|
|
13265
13635
|
} else {
|
|
13266
13636
|
result = {
|
|
13267
13637
|
sessions: await listSessionBrowse(params),
|
|
@@ -13274,8 +13644,8 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
13274
13644
|
{
|
|
13275
13645
|
const usageParams = isPlainObject(params) ? params : {};
|
|
13276
13646
|
const source = typeof usageParams.source === 'string' ? usageParams.source.trim().toLowerCase() : '';
|
|
13277
|
-
if (source && source !== 'codex' && source !== 'claude' && source !== 'gemini' && source !== 'codebuddy' && source !== 'all') {
|
|
13278
|
-
result = { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, or all' };
|
|
13647
|
+
if (source && source !== 'codex' && source !== 'claude' && source !== 'gemini' && source !== 'codebuddy' && source !== 'pi' && source !== 'all') {
|
|
13648
|
+
result = { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, pi, or all' };
|
|
13279
13649
|
} else {
|
|
13280
13650
|
result = {
|
|
13281
13651
|
sessions: await listSessionUsage({
|
|
@@ -13291,8 +13661,8 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
13291
13661
|
{
|
|
13292
13662
|
const usageParams = isPlainObject(params) ? params : {};
|
|
13293
13663
|
const source = typeof usageParams.source === 'string' ? usageParams.source.trim().toLowerCase() : '';
|
|
13294
|
-
if (source && source !== 'codex' && source !== 'claude' && source !== 'gemini' && source !== 'codebuddy' && source !== 'all') {
|
|
13295
|
-
result = { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, or all' };
|
|
13664
|
+
if (source && source !== 'codex' && source !== 'claude' && source !== 'gemini' && source !== 'codebuddy' && source !== 'pi' && source !== 'all') {
|
|
13665
|
+
result = { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, pi, or all' };
|
|
13296
13666
|
} else {
|
|
13297
13667
|
result = await exportSessionUsage({
|
|
13298
13668
|
...usageParams,
|
|
@@ -13304,8 +13674,8 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
13304
13674
|
case 'list-session-paths':
|
|
13305
13675
|
{
|
|
13306
13676
|
const source = typeof params.source === 'string' ? params.source.trim().toLowerCase() : '';
|
|
13307
|
-
if (source && source !== 'codex' && source !== 'claude' && source !== 'gemini' && source !== 'codebuddy' && source !== 'all') {
|
|
13308
|
-
result = { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, or all' };
|
|
13677
|
+
if (source && source !== 'codex' && source !== 'claude' && source !== 'gemini' && source !== 'codebuddy' && source !== 'pi' && source !== 'all') {
|
|
13678
|
+
result = { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, pi, or all' };
|
|
13309
13679
|
} else {
|
|
13310
13680
|
result = {
|
|
13311
13681
|
paths: listSessionPaths(params)
|
|
@@ -15564,7 +15934,7 @@ function buildMcpClaudeSettingsPayload() {
|
|
|
15564
15934
|
function normalizeMcpSource(value) {
|
|
15565
15935
|
const source = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
15566
15936
|
if (!source) return '';
|
|
15567
|
-
if (source === 'codex' || source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'all') {
|
|
15937
|
+
if (source === 'codex' || source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'pi' || source === 'all') {
|
|
15568
15938
|
return source;
|
|
15569
15939
|
}
|
|
15570
15940
|
return null;
|
|
@@ -15877,7 +16247,7 @@ function createWorkflowToolCatalog() {
|
|
|
15877
16247
|
handler: async (args = {}) => {
|
|
15878
16248
|
const source = normalizeMcpSource(args.source);
|
|
15879
16249
|
if (source === null) {
|
|
15880
|
-
return { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, or all' };
|
|
16250
|
+
return { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, pi, or all' };
|
|
15881
16251
|
}
|
|
15882
16252
|
return {
|
|
15883
16253
|
source: source || 'all',
|
|
@@ -17997,7 +18367,7 @@ function createMcpTools(options = {}) {
|
|
|
17997
18367
|
const input = args && typeof args === 'object' ? args : {};
|
|
17998
18368
|
const source = normalizeMcpSource(input.source);
|
|
17999
18369
|
if (source === null) {
|
|
18000
|
-
return { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, or all' };
|
|
18370
|
+
return { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, pi, or all' };
|
|
18001
18371
|
}
|
|
18002
18372
|
const normalizedInput = {
|
|
18003
18373
|
...input,
|
|
@@ -18457,7 +18827,7 @@ function createMcpResources() {
|
|
|
18457
18827
|
contents: [{
|
|
18458
18828
|
uri,
|
|
18459
18829
|
mimeType: 'application/json',
|
|
18460
|
-
text: JSON.stringify({ error: 'Invalid source. Must be codex, claude, gemini, codebuddy, or all' }, null, 2)
|
|
18830
|
+
text: JSON.stringify({ error: 'Invalid source. Must be codex, claude, gemini, codebuddy, pi, or all' }, null, 2)
|
|
18461
18831
|
}]
|
|
18462
18832
|
};
|
|
18463
18833
|
}
|
|
@@ -18707,7 +19077,7 @@ function printMainHelp() {
|
|
|
18707
19077
|
console.log(' 注: follow-up 自动排队仅支持 linux/android/netbsd/openbsd/darwin/freebsd 且 stdin 必须是 TTY,其他平台会报错');
|
|
18708
19078
|
console.log(' codexmate qwen [参数...] 等同于 qwen --yolo');
|
|
18709
19079
|
console.log(' codexmate mcp [serve] [--transport stdio] [--allow-write|--read-only]');
|
|
18710
|
-
console.log(' codexmate export-session --source <codex|claude|gemini|codebuddy> (--session-id <ID>|--file <PATH>) [--output <PATH>] [--max-messages <N|all|Infinity>]');
|
|
19080
|
+
console.log(' codexmate export-session --source <codex|claude|gemini|codebuddy|pi> (--session-id <ID>|--file <PATH>) [--output <PATH>] [--max-messages <N|all|Infinity>]');
|
|
18711
19081
|
console.log(' codexmate convert-session --from <codex|claude> --to <codex|claude> (--session-id <ID>|--file <PATH>) [--output <PATH>] [--max-messages <N|all|Infinity>]');
|
|
18712
19082
|
console.log(' codexmate zip <路径> [--max:级别] 压缩(系统 zip 优先,其次 zip-lib)');
|
|
18713
19083
|
console.log(' codexmate unzip <zip文件> [输出目录] 解压(zip-lib)');
|
package/lib/cli-sessions.js
CHANGED
|
@@ -204,6 +204,17 @@ function extractMessageFromRecord(record, source) {
|
|
|
204
204
|
}
|
|
205
205
|
return null;
|
|
206
206
|
}
|
|
207
|
+
if (source === 'pi') {
|
|
208
|
+
if (record.type === 'message' && record.message && typeof record.message === 'object') {
|
|
209
|
+
const role = normalizeRole(record.message.role);
|
|
210
|
+
const text = extractMessageText(record.message.content);
|
|
211
|
+
if (!role || !text) {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
return { role, text };
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
207
218
|
|
|
208
219
|
const role = normalizeRole(record.type);
|
|
209
220
|
if (!role) {
|
|
@@ -271,6 +282,14 @@ function applySessionDetailRecordMetadata(record, source, state) {
|
|
|
271
282
|
return;
|
|
272
283
|
}
|
|
273
284
|
|
|
285
|
+
if (source === 'pi') {
|
|
286
|
+
if (record.type === 'session') {
|
|
287
|
+
state.sessionId = record.id || state.sessionId;
|
|
288
|
+
state.cwd = record.cwd || state.cwd;
|
|
289
|
+
}
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
274
293
|
if (!state.sessionId && record.sessionId) {
|
|
275
294
|
state.sessionId = record.sessionId;
|
|
276
295
|
}
|
package/package.json
CHANGED
|
@@ -29,14 +29,14 @@ function shouldUseFastSessionBrowseLimit(options = {}) {
|
|
|
29
29
|
|
|
30
30
|
export function isSessionQueryEnabled(source) {
|
|
31
31
|
const normalized = normalizeSessionSource(source, '');
|
|
32
|
-
return normalized === 'codex' || normalized === 'claude' || normalized === 'gemini' || normalized === 'codebuddy' || normalized === 'all';
|
|
32
|
+
return normalized === 'codex' || normalized === 'claude' || normalized === 'gemini' || normalized === 'codebuddy' || normalized === 'pi' || normalized === 'all';
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
export function normalizeSessionSource(source, fallback = 'all') {
|
|
36
36
|
const normalized = typeof source === 'string'
|
|
37
37
|
? source.trim().toLowerCase()
|
|
38
38
|
: '';
|
|
39
|
-
if (normalized === 'codex' || normalized === 'claude' || normalized === 'gemini' || normalized === 'codebuddy' || normalized === 'all') {
|
|
39
|
+
if (normalized === 'codex' || normalized === 'claude' || normalized === 'gemini' || normalized === 'codebuddy' || normalized === 'pi' || normalized === 'all') {
|
|
40
40
|
return normalized;
|
|
41
41
|
}
|
|
42
42
|
return fallback;
|
|
@@ -209,7 +209,7 @@ export function buildUsageHeatmap(sessions = [], options = {}) {
|
|
|
209
209
|
for (const session of list) {
|
|
210
210
|
if (!session || typeof session !== 'object') continue;
|
|
211
211
|
const source = normalizeSessionSource(session.source, '');
|
|
212
|
-
if (source !== 'codex' && source !== 'claude') continue;
|
|
212
|
+
if (source !== 'codex' && source !== 'claude' && source !== 'pi') continue;
|
|
213
213
|
const updatedAtMs = Date.parse(session.updatedAt || '');
|
|
214
214
|
if (!Number.isFinite(updatedAtMs)) continue;
|
|
215
215
|
normalized.push({
|
|
@@ -309,7 +309,7 @@ export function buildUsageHourlyHeatmap(sessions = [], options = {}) {
|
|
|
309
309
|
for (const session of list) {
|
|
310
310
|
if (!session || typeof session !== 'object') continue;
|
|
311
311
|
const source = normalizeSessionSource(session.source, '');
|
|
312
|
-
if (source !== 'codex' && source !== 'claude') continue;
|
|
312
|
+
if (source !== 'codex' && source !== 'claude' && source !== 'pi') continue;
|
|
313
313
|
const updatedAtMs = Date.parse(session.updatedAt || '');
|
|
314
314
|
if (!Number.isFinite(updatedAtMs)) continue;
|
|
315
315
|
const dayStart = toUtcDayStartMs(updatedAtMs);
|
|
@@ -402,7 +402,7 @@ export function buildUsageChartGroups(sessions = [], options = {}) {
|
|
|
402
402
|
for (const [sessionIndex, session] of list.entries()) {
|
|
403
403
|
if (!session || typeof session !== 'object') continue;
|
|
404
404
|
const source = normalizeSessionSource(session.source, '');
|
|
405
|
-
if (source !== 'codex' && source !== 'claude') continue;
|
|
405
|
+
if (source !== 'codex' && source !== 'claude' && source !== 'pi') continue;
|
|
406
406
|
const updatedAtMs = Date.parse(session.updatedAt || '');
|
|
407
407
|
if (!Number.isFinite(updatedAtMs)) continue;
|
|
408
408
|
const createdAtMs = Date.parse(session.createdAt || '');
|
|
@@ -307,7 +307,8 @@ export function createSessionComputed() {
|
|
|
307
307
|
{ value: "codex", label: this.t("sessions.source.codex") },
|
|
308
308
|
{ value: "claude", label: this.t("sessions.source.claudeCode") },
|
|
309
309
|
{ value: "gemini", label: this.t("sessions.source.gemini") },
|
|
310
|
-
{ value: "codebuddy", label: this.t("sessions.source.codebuddy") }
|
|
310
|
+
{ value: "codebuddy", label: this.t("sessions.source.codebuddy") },
|
|
311
|
+
{ value: "pi", label: this.t("sessions.source.pi") }
|
|
311
312
|
];
|
|
312
313
|
},
|
|
313
314
|
activeSessionExportKey() {
|
|
@@ -28,8 +28,8 @@ export function createSessionActionMethods(options = {}) {
|
|
|
28
28
|
let error = '';
|
|
29
29
|
if (!source) {
|
|
30
30
|
error = '缺少 source 参数';
|
|
31
|
-
} else if (source !== 'codex' && source !== 'claude') {
|
|
32
|
-
error = 'source 仅支持 codex 或
|
|
31
|
+
} else if (source !== 'codex' && source !== 'claude' && source !== 'gemini' && source !== 'codebuddy' && source !== 'pi') {
|
|
32
|
+
error = 'source 仅支持 codex、claude、gemini、codebuddy 或 pi';
|
|
33
33
|
}
|
|
34
34
|
if (!sessionId && !filePath) {
|
|
35
35
|
error = error ? `${error},还缺少 sessionId 或 filePath` : '缺少 sessionId 或 filePath 参数';
|
|
@@ -67,7 +67,15 @@ export function createSessionActionMethods(options = {}) {
|
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
const sourceLabel = context.params.source === 'codex'
|
|
70
|
+
const sourceLabel = context.params.source === 'codex'
|
|
71
|
+
? 'Codex'
|
|
72
|
+
: (context.params.source === 'claude'
|
|
73
|
+
? 'Claude Code'
|
|
74
|
+
: (context.params.source === 'gemini'
|
|
75
|
+
? 'Gemini CLI'
|
|
76
|
+
: (context.params.source === 'pi'
|
|
77
|
+
? 'Pi'
|
|
78
|
+
: (context.params.source === 'codebuddy' ? 'CodeBuddy Code' : context.params.source))));
|
|
71
79
|
this.activeSession = {
|
|
72
80
|
source: context.params.source,
|
|
73
81
|
sourceLabel,
|
|
@@ -96,7 +104,7 @@ export function createSessionActionMethods(options = {}) {
|
|
|
96
104
|
buildSessionStandaloneUrl(session) {
|
|
97
105
|
if (!session) return '';
|
|
98
106
|
const source = typeof session.source === 'string' ? session.source.trim().toLowerCase() : '';
|
|
99
|
-
if (!source || (source !== 'codex' && source !== 'claude')) return '';
|
|
107
|
+
if (!source || (source !== 'codex' && source !== 'claude' && source !== 'gemini' && source !== 'codebuddy' && source !== 'pi')) return '';
|
|
100
108
|
const sessionId = typeof session.sessionId === 'string' ? session.sessionId.trim() : '';
|
|
101
109
|
const filePath = typeof session.filePath === 'string' ? session.filePath.trim() : '';
|
|
102
110
|
if (!sessionId && !filePath) return '';
|
|
@@ -120,7 +120,7 @@ export function createSessionBrowserMethods(options = {}) {
|
|
|
120
120
|
syncSessionPathOptionsForSource(source, nextOptions, mergeWithExisting = false) {
|
|
121
121
|
const targetSource = source === 'claude'
|
|
122
122
|
? 'claude'
|
|
123
|
-
: (source === 'gemini' ? 'gemini' : (source === 'all' ? 'all' : 'codex'));
|
|
123
|
+
: (source === 'gemini' ? 'gemini' : (source === 'pi' ? 'pi' : (source === 'all' ? 'all' : 'codex')));
|
|
124
124
|
const current = Array.isArray(this.sessionPathOptionsMap[targetSource])
|
|
125
125
|
? this.sessionPathOptionsMap[targetSource]
|
|
126
126
|
: [];
|
|
@@ -137,7 +137,7 @@ export function createSessionBrowserMethods(options = {}) {
|
|
|
137
137
|
refreshSessionPathOptions(source) {
|
|
138
138
|
const targetSource = source === 'claude'
|
|
139
139
|
? 'claude'
|
|
140
|
-
: (source === 'gemini' ? 'gemini' : (source === 'all' ? 'all' : 'codex'));
|
|
140
|
+
: (source === 'gemini' ? 'gemini' : (source === 'pi' ? 'pi' : (source === 'all' ? 'all' : 'codex')));
|
|
141
141
|
const base = Array.isArray(this.sessionPathOptionsMap[targetSource])
|
|
142
142
|
? [...this.sessionPathOptionsMap[targetSource]]
|
|
143
143
|
: [];
|
|
@@ -161,7 +161,7 @@ export function createSessionBrowserMethods(options = {}) {
|
|
|
161
161
|
async loadSessionPathOptions(options = {}) {
|
|
162
162
|
const source = options.source === 'claude'
|
|
163
163
|
? 'claude'
|
|
164
|
-
: (options.source === 'gemini' ? 'gemini' : (options.source === 'all' ? 'all' : 'codex'));
|
|
164
|
+
: (options.source === 'gemini' ? 'gemini' : (options.source === 'pi' ? 'pi' : (options.source === 'all' ? 'all' : 'codex')));
|
|
165
165
|
const forceRefresh = !!options.forceRefresh;
|
|
166
166
|
const loaded = !!this.sessionPathOptionsLoadedMap[source];
|
|
167
167
|
if (!forceRefresh && loaded) {
|
|
@@ -507,7 +507,11 @@ export function createSessionBrowserMethods(options = {}) {
|
|
|
507
507
|
? this.t('sessions.source.claudeCode')
|
|
508
508
|
: (this.sessionFilterSource === 'gemini'
|
|
509
509
|
? this.t('sessions.source.gemini')
|
|
510
|
-
: (this.sessionFilterSource === 'codebuddy'
|
|
510
|
+
: (this.sessionFilterSource === 'codebuddy'
|
|
511
|
+
? this.t('sessions.source.codebuddy')
|
|
512
|
+
: (this.sessionFilterSource === 'pi'
|
|
513
|
+
? this.t('sessions.source.pi')
|
|
514
|
+
: this.sessionFilterSource))));
|
|
511
515
|
chips.push({ key: 'source', title: this.t('sessions.filters.source'), value: label });
|
|
512
516
|
}
|
|
513
517
|
if (this.sessionPathFilter) {
|
|
@@ -10,7 +10,7 @@ export function createSessionTrashMethods(options = {}) {
|
|
|
10
10
|
const deletedAt = typeof result.deletedAt === 'string' && result.deletedAt
|
|
11
11
|
? result.deletedAt
|
|
12
12
|
: new Date().toISOString();
|
|
13
|
-
const source = session && (session.source === 'claude' || session.source === 'gemini')
|
|
13
|
+
const source = session && (session.source === 'claude' || session.source === 'gemini' || session.source === 'codebuddy' || session.source === 'pi')
|
|
14
14
|
? session.source
|
|
15
15
|
: 'codex';
|
|
16
16
|
return {
|
|
@@ -18,7 +18,7 @@ export function createSessionTrashMethods(options = {}) {
|
|
|
18
18
|
source,
|
|
19
19
|
sourceLabel: session && typeof session.sourceLabel === 'string' && session.sourceLabel
|
|
20
20
|
? session.sourceLabel
|
|
21
|
-
: (source === 'claude' ? 'Claude Code' : (source === 'gemini' ? 'Gemini CLI' : 'Codex')),
|
|
21
|
+
: (source === 'claude' ? 'Claude Code' : (source === 'gemini' ? 'Gemini CLI' : (source === 'pi' ? 'Pi' : (source === 'codebuddy' ? 'CodeBuddy Code' : 'Codex')))),
|
|
22
22
|
sessionId: session && typeof session.sessionId === 'string' ? session.sessionId : '',
|
|
23
23
|
title: session && typeof session.title === 'string' && session.title
|
|
24
24
|
? session.title
|
|
@@ -645,6 +645,7 @@ const en = Object.freeze({
|
|
|
645
645
|
'sessions.source.claudeCode': 'Claude Code',
|
|
646
646
|
'sessions.source.gemini': 'Gemini CLI',
|
|
647
647
|
'sessions.source.codebuddy': 'CodeBuddy Code',
|
|
648
|
+
'sessions.source.pi': 'Pi',
|
|
648
649
|
'sessions.loadingList': 'Loading sessions...',
|
|
649
650
|
'sessions.empty': 'No sessions found',
|
|
650
651
|
'sessions.unknownTime': 'unknown time',
|
|
@@ -647,6 +647,7 @@ const ja = Object.freeze({
|
|
|
647
647
|
'sessions.source.claudeCode': 'Claude Code',
|
|
648
648
|
'sessions.source.gemini': 'Gemini',
|
|
649
649
|
'sessions.source.codebuddy': 'CodeBuddy',
|
|
650
|
+
'sessions.source.pi': 'Pi',
|
|
650
651
|
'sessions.loadingList': 'セッション一覧を読み込み中...',
|
|
651
652
|
'sessions.empty': 'セッションがありません',
|
|
652
653
|
'sessions.unknownTime': '不明な時間',
|
|
@@ -850,6 +850,7 @@ const vi = Object.freeze({
|
|
|
850
850
|
'sessions.source.claudeCode': 'Claude Code',
|
|
851
851
|
'sessions.source.gemini': 'Gemini CLI',
|
|
852
852
|
'sessions.source.codebuddy': 'CodeBuddy Code',
|
|
853
|
+
'sessions.source.pi': 'Pi',
|
|
853
854
|
'sessions.loadingList': 'Đang tải phiên làm việc...',
|
|
854
855
|
'sessions.empty': 'Không tìm thấy phiên nào',
|
|
855
856
|
'sessions.unknownTime': 'thời gian không rõ',
|
|
@@ -645,6 +645,7 @@ const zhTw = Object.freeze({
|
|
|
645
645
|
'sessions.source.claudeCode': 'Claude Code',
|
|
646
646
|
'sessions.source.gemini': 'Gemini CLI',
|
|
647
647
|
'sessions.source.codebuddy': 'CodeBuddy Code',
|
|
648
|
+
'sessions.source.pi': 'Pi',
|
|
648
649
|
'sessions.loadingList': '會話載入中...',
|
|
649
650
|
'sessions.empty': '暫無可用會話記錄',
|
|
650
651
|
'sessions.unknownTime': '未知時間',
|
|
@@ -645,6 +645,7 @@ const zh = Object.freeze({
|
|
|
645
645
|
'sessions.source.claudeCode': 'Claude Code',
|
|
646
646
|
'sessions.source.gemini': 'Gemini CLI',
|
|
647
647
|
'sessions.source.codebuddy': 'CodeBuddy Code',
|
|
648
|
+
'sessions.source.pi': 'Pi',
|
|
648
649
|
'sessions.loadingList': '会话加载中...',
|
|
649
650
|
'sessions.empty': '暂无可用会话记录',
|
|
650
651
|
'sessions.unknownTime': '未知时间',
|