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 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, and CodeBuddy Code from one place.
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, and CodeBuddy Code |
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ừ tất cả tool trong một nơi duy nhấ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 与 CodeBuddy Code 的本地会话。
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 与 CodeBuddy Code 的本地会话进行检索、预览、筛选与导出 |
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,
@@ -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
- : parseCodexSessionSummary(filePath, summaryOptions)));
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' ? 'codebuddy' : 'all')));
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' ? 'gemini' : (item.source === 'codebuddy' ? 'codebuddy' : '')));
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' ? 'gemini' : (item.source === 'codebuddy' ? 'codebuddy' : '')));
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' ? 'claude' : 'codex';
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
- : listCodexSessions(limit, scanOptions)));
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' ? 'claude' : 'codex';
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
- : [getCodexSessionsDir(), derivedCodexDir]));
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' ? 'Gemini CLI' : (source === 'codebuddy' ? 'CodeBuddy Code' : 'Codex'));
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' ? 'gemini' : (source === 'codebuddy' ? 'codebuddy' : 'codex')),
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' ? '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' ? 'Gemini CLI' : (source === 'codebuddy' ? 'CodeBuddy Code' : 'Codex')),
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' ? 'claude' : 'codex';
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' ? 'Claude Code' : 'Codex';
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' ? getCodeBuddyProjectsDir() : getCodexSessionsDir()));
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' ? 'codebuddy' : 'all')));
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' ? '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' ? parseCodeBuddySessionSummary(filePath) : parseCodexSessionSummary(filePath))))
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' ? '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' ? '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' ? 'Gemini CLI' : 'CodeBuddy Code'));
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' ? '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' ? 'Gemini CLI' : 'CodeBuddy Code'));
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' ? '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' ? 'Gemini CLI' : 'CodeBuddy Code'));
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 或 claude');
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)');
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codexmate",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Codex/Claude Code/OpenClaw 配置、会话与任务编排 CLI + Web 工具",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -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 或 claude';
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' ? 'Codex' : 'Claude Code';
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' ? this.t('sessions.source.codebuddy') : this.sessionFilterSource)));
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': '未知时间',