codexmate 0.1.3 → 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/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();
@@ -909,8 +912,8 @@ function isPlainObject(value) {
909
912
  return !!value && typeof value === 'object' && !Array.isArray(value);
910
913
  }
911
914
 
912
- const TOOL_CONFIG_PERMISSION_TARGETS = new Set(['codex', 'claude', 'opencode', 'kilocode']);
913
- const TOOL_CONFIG_PERMISSION_DEFAULTS = Object.freeze({ codex: false, claude: false, opencode: false, kilocode: false });
915
+ const TOOL_CONFIG_PERMISSION_TARGETS = new Set(['codex', 'claude', 'opencode', 'kilocode', 'openclaw']);
916
+ const TOOL_CONFIG_PERMISSION_DEFAULTS = Object.freeze({ codex: false, claude: false, opencode: false, kilocode: false, openclaw: false });
914
917
  let toolConfigWriteGuardDepth = 0;
915
918
 
916
919
  function enterToolConfigWriteGuard() {
@@ -938,7 +941,8 @@ function normalizeToolConfigPermissions(value) {
938
941
  codex: source.codex === true,
939
942
  claude: source.claude === true,
940
943
  opencode: source.opencode === true,
941
- kilocode: source.kilocode === true
944
+ kilocode: source.kilocode === true,
945
+ openclaw: source.openclaw === true
942
946
  };
943
947
  }
944
948
 
@@ -1016,7 +1020,7 @@ function normalizeSidebarCollapsedPreference(value) {
1016
1020
 
1017
1021
  function normalizeSessionFilterSourcePreference(value) {
1018
1022
  const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
1019
- 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;
1020
1024
  return 'codex';
1021
1025
  }
1022
1026
 
@@ -1197,10 +1201,16 @@ function getApiToolConfigWriteTarget(action) {
1197
1201
  'apply-kilocode-config',
1198
1202
  'start-kilocode'
1199
1203
  ]);
1204
+ const openclawWriteActions = new Set([
1205
+ 'apply-openclaw-config',
1206
+ 'apply-openclaw-agents-file',
1207
+ 'apply-openclaw-workspace-file'
1208
+ ]);
1200
1209
  if (codexWriteActions.has(name)) return 'codex';
1201
1210
  if (claudeWriteActions.has(name)) return 'claude';
1202
1211
  if (opencodeWriteActions.has(name)) return 'opencode';
1203
1212
  if (kilocodeWriteActions.has(name)) return 'kilocode';
1213
+ if (openclawWriteActions.has(name)) return 'openclaw';
1204
1214
  return '';
1205
1215
  }
1206
1216
 
@@ -1816,6 +1826,20 @@ function getCodeBuddyProjectsDir() {
1816
1826
  return resolveExistingDir(candidates, CODEBUDDY_PROJECTS_DIR);
1817
1827
  }
1818
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
+
1819
1843
  function getCodexmateDerivedSessionsRoot(target) {
1820
1844
  if (target === 'claude') {
1821
1845
  return CODEXMATE_DERIVED_CLAUDE_DIR;
@@ -3835,6 +3859,15 @@ async function countConversationMessagesInFile(filePath, source) {
3835
3859
  role = '';
3836
3860
  }
3837
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
+ }
3838
3871
  } else {
3839
3872
  role = normalizeRole(record.type);
3840
3873
  if (role === 'assistant' || role === 'user' || role === 'system') {
@@ -4003,7 +4036,9 @@ async function hydrateSessionTrashEntries(entries, options = {}) {
4003
4036
  ? 'codex'
4004
4037
  : (options.source === 'gemini'
4005
4038
  ? 'gemini'
4006
- : (options.source === 'codebuddy' ? 'codebuddy' : 'all')));
4039
+ : (options.source === 'codebuddy'
4040
+ ? 'codebuddy'
4041
+ : (options.source === 'pi' ? 'pi' : 'all'))));
4007
4042
  const hydratedEntries = await mapWithConcurrency(Array.isArray(entries) ? entries : [], 8, async (entry) => {
4008
4043
  const normalizedEntry = normalizeSessionTrashEntry(entry);
4009
4044
  if (!normalizedEntry) {
@@ -4012,7 +4047,7 @@ async function hydrateSessionTrashEntries(entries, options = {}) {
4012
4047
  return await resolveSessionTrashEntryExactMessageCount(normalizedEntry);
4013
4048
  });
4014
4049
 
4015
- if (source === 'codex' || source === 'claude' || source === 'gemini' || source === 'codebuddy') {
4050
+ if (source === 'codex' || source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'pi') {
4016
4051
  return hydratedEntries.filter((entry) => entry.source === source);
4017
4052
  }
4018
4053
  return hydratedEntries;
@@ -4030,7 +4065,11 @@ async function hydrateSessionItemsExactMessageCount(items) {
4030
4065
  ? 'claude'
4031
4066
  : (item.source === 'codex'
4032
4067
  ? 'codex'
4033
- : (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' : ''))));
4034
4073
  const filePath = typeof item.filePath === 'string' ? item.filePath : '';
4035
4074
  if (!source || !filePath || !fs.existsSync(filePath)) {
4036
4075
  return item;
@@ -4078,7 +4117,11 @@ async function readSessionMessageCounts(params = {}) {
4078
4117
  ? 'claude'
4079
4118
  : (item.source === 'codex'
4080
4119
  ? 'codex'
4081
- : (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' : ''))));
4082
4125
  const filePath = typeof item.filePath === 'string' ? item.filePath : '';
4083
4126
  if (!source || !filePath || !fs.existsSync(filePath)) {
4084
4127
  return { key };
@@ -4592,7 +4635,13 @@ function setSessionListCache(cacheKey, value) {
4592
4635
  }
4593
4636
 
4594
4637
  function buildSessionInventoryCacheKey(source, limit, options = {}) {
4595
- 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')));
4596
4645
  const normalizedLimit = Number.isFinite(Number(limit))
4597
4646
  ? Math.max(1, Math.floor(Number(limit)))
4598
4647
  : 1;
@@ -4678,7 +4727,7 @@ function getSessionInventoryCache(cacheKey, forceRefresh = false) {
4678
4727
  }
4679
4728
 
4680
4729
  function registerSessionFileLookupEntries(source, sessions = []) {
4681
- const normalizedSource = source === 'claude' || source === 'gemini' || source === 'codebuddy'
4730
+ const normalizedSource = source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'pi'
4682
4731
  ? source
4683
4732
  : 'codex';
4684
4733
  const store = g_sessionFileLookupCache[normalizedSource];
@@ -4719,7 +4768,7 @@ function setSessionInventoryCache(cacheKey, source, value) {
4719
4768
  }
4720
4769
 
4721
4770
  function listSessionInventoryBySource(source, limit, scanOptions = {}, options = {}) {
4722
- const normalizedSource = source === 'claude' || source === 'gemini' || source === 'codebuddy'
4771
+ const normalizedSource = source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'pi'
4723
4772
  ? source
4724
4773
  : 'codex';
4725
4774
  const forceRefresh = !!options.forceRefresh;
@@ -4735,7 +4784,9 @@ function listSessionInventoryBySource(source, limit, scanOptions = {}, options =
4735
4784
  ? listGeminiSessions(limit, scanOptions)
4736
4785
  : (normalizedSource === 'codebuddy'
4737
4786
  ? listCodeBuddySessions(limit, scanOptions)
4738
- : listCodexSessions(limit, scanOptions)));
4787
+ : (normalizedSource === 'pi'
4788
+ ? listPiSessions(limit, scanOptions)
4789
+ : listCodexSessions(limit, scanOptions))));
4739
4790
  setSessionInventoryCache(cacheKey, normalizedSource, sessions);
4740
4791
  return sessions;
4741
4792
  }
@@ -4747,7 +4798,8 @@ function invalidateSessionListCache() {
4747
4798
  codex: new Map(),
4748
4799
  claude: new Map(),
4749
4800
  gemini: new Map(),
4750
- codebuddy: new Map()
4801
+ codebuddy: new Map(),
4802
+ pi: new Map()
4751
4803
  };
4752
4804
  }
4753
4805
 
@@ -4981,7 +5033,13 @@ function readSessionProviderFromRecord(record, source = '') {
4981
5033
  if (provider) {
4982
5034
  return provider;
4983
5035
  }
4984
- 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')));
4985
5043
  }
4986
5044
 
4987
5045
  function applySessionUsageSummaryFromRecord(state, record, source) {
@@ -5680,6 +5738,177 @@ function parseGeminiSessionSummary(filePath, options = {}) {
5680
5738
  };
5681
5739
  }
5682
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
+
5683
5912
  function listCodexSessions(limit, options = {}) {
5684
5913
  const codexSessionsDir = getCodexSessionsDir();
5685
5914
  const scanFactor = Number.isFinite(Number(options.scanFactor))
@@ -6119,8 +6348,56 @@ function listCodeBuddySessions(limit, options = {}) {
6119
6348
  return mergeAndLimitSessions(sessions, limit);
6120
6349
  }
6121
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
+
6122
6399
  async function listAllSessions(params = {}) {
6123
- 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'
6124
6401
  ? params.source
6125
6402
  : 'all';
6126
6403
  const rawLimit = Number(params.limit);
@@ -6170,6 +6447,9 @@ async function listAllSessions(params = {}) {
6170
6447
  if (source === 'all' || source === 'codebuddy') {
6171
6448
  sessions = sessions.concat(listSessionInventoryBySource('codebuddy', limit, scanOptions, { forceRefresh }));
6172
6449
  }
6450
+ if (source === 'all' || source === 'pi') {
6451
+ sessions = sessions.concat(listSessionInventoryBySource('pi', limit, scanOptions, { forceRefresh }));
6452
+ }
6173
6453
 
6174
6454
  if (hasPathFilter) {
6175
6455
  sessions = sessions.filter(item => matchesSessionPathFilter(item, normalizedPathFilter));
@@ -6252,6 +6532,7 @@ async function listSessionUsage(params = {}) {
6252
6532
  parseClaudeSessionSummary,
6253
6533
  parseCodeBuddySessionSummary,
6254
6534
  parseGeminiSessionSummary,
6535
+ parsePiSessionSummary,
6255
6536
  MAX_SESSION_USAGE_LIST_SIZE,
6256
6537
  SESSION_BROWSE_SUMMARY_READ_BYTES
6257
6538
  });
@@ -6265,10 +6546,10 @@ async function exportSessionUsage(params = {}) {
6265
6546
 
6266
6547
  function listSessionPaths(params = {}) {
6267
6548
  const source = typeof params.source === 'string' ? params.source.trim().toLowerCase() : '';
6268
- 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') {
6269
6550
  return [];
6270
6551
  }
6271
- 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';
6272
6553
  const rawLimit = Number(params.limit);
6273
6554
  const limit = Number.isFinite(rawLimit)
6274
6555
  ? Math.max(1, Math.min(rawLimit, MAX_SESSION_PATH_LIST_SIZE))
@@ -6302,6 +6583,9 @@ function listSessionPaths(params = {}) {
6302
6583
  if (validSource === 'all' || validSource === 'codebuddy') {
6303
6584
  sessions = sessions.concat(listSessionInventoryBySource('codebuddy', gatherLimit, scanOptions, { forceRefresh }));
6304
6585
  }
6586
+ if (validSource === 'all' || validSource === 'pi') {
6587
+ sessions = sessions.concat(listSessionInventoryBySource('pi', gatherLimit, scanOptions, { forceRefresh }));
6588
+ }
6305
6589
 
6306
6590
  const dedupedPaths = [];
6307
6591
  const seen = new Set();
@@ -6327,7 +6611,7 @@ function listSessionPaths(params = {}) {
6327
6611
  }
6328
6612
 
6329
6613
  function resolveSessionFilePath(source, filePath, sessionId) {
6330
- const normalizedSource = source === 'claude' || source === 'gemini' || source === 'codebuddy'
6614
+ const normalizedSource = source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'pi'
6331
6615
  ? source
6332
6616
  : 'codex';
6333
6617
  const homeDir = process && process.env && process.env.HOME ? process.env.HOME : '';
@@ -6339,7 +6623,9 @@ function resolveSessionFilePath(source, filePath, sessionId) {
6339
6623
  ? [getGeminiTmpDir()]
6340
6624
  : (normalizedSource === 'codebuddy'
6341
6625
  ? [getCodeBuddyProjectsDir()]
6342
- : [getCodexSessionsDir(), derivedCodexDir]));
6626
+ : (normalizedSource === 'pi'
6627
+ ? [getPiSessionsDir()]
6628
+ : [getCodexSessionsDir(), derivedCodexDir])));
6343
6629
  const availableRoots = roots.filter((dirPath) => dirPath && fs.existsSync(dirPath));
6344
6630
  if (availableRoots.length === 0) {
6345
6631
  return '';
@@ -6887,13 +7173,21 @@ function buildSessionSummaryFallback(source, filePath, sessionId = '') {
6887
7173
  const resolvedSessionId = sessionId || path.basename(filePath, '.jsonl');
6888
7174
  const sourceLabel = source === 'claude'
6889
7175
  ? 'Claude Code'
6890
- : (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')));
6891
7181
  return {
6892
7182
  source,
6893
7183
  sourceLabel,
6894
7184
  provider: source === 'claude'
6895
7185
  ? 'claude'
6896
- : (source === 'gemini' ? 'gemini' : (source === 'codebuddy' ? 'codebuddy' : 'codex')),
7186
+ : (source === 'gemini'
7187
+ ? 'gemini'
7188
+ : (source === 'codebuddy'
7189
+ ? 'codebuddy'
7190
+ : (source === 'pi' ? 'pi' : 'codex'))),
6897
7191
  sessionId: resolvedSessionId,
6898
7192
  title: resolvedSessionId,
6899
7193
  cwd: '',
@@ -6946,7 +7240,9 @@ function normalizeSessionTrashEntry(entry) {
6946
7240
  ? 'codex'
6947
7241
  : (entry.source === 'gemini'
6948
7242
  ? 'gemini'
6949
- : (entry.source === 'codebuddy' ? 'codebuddy' : '')));
7243
+ : (entry.source === 'codebuddy'
7244
+ ? 'codebuddy'
7245
+ : (entry.source === 'pi' ? 'pi' : ''))));
6950
7246
  const trashId = typeof entry.trashId === 'string' ? entry.trashId.trim() : '';
6951
7247
  if (!source || !trashId || trashId.includes('/') || trashId.includes('\\') || trashId.includes('\0')) {
6952
7248
  return null;
@@ -6963,7 +7259,11 @@ function normalizeSessionTrashEntry(entry) {
6963
7259
  source,
6964
7260
  sourceLabel: source === 'claude'
6965
7261
  ? 'Claude Code'
6966
- : (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'))),
6967
7267
  sessionId: sessionId || trashId,
6968
7268
  title: typeof entry.title === 'string' && entry.title.trim() ? entry.title.trim() : (sessionId || trashId),
6969
7269
  cwd: typeof entry.cwd === 'string' ? entry.cwd : '',
@@ -6979,7 +7279,7 @@ function normalizeSessionTrashEntry(entry) {
6979
7279
  originalFilePath: typeof entry.originalFilePath === 'string' ? entry.originalFilePath : '',
6980
7280
  provider: typeof entry.provider === 'string' && entry.provider.trim()
6981
7281
  ? entry.provider.trim()
6982
- : (source === 'claude' ? 'claude' : (source === 'gemini' ? 'gemini' : (source === 'codebuddy' ? 'codebuddy' : 'codex'))),
7282
+ : (source === 'claude' ? 'claude' : (source === 'gemini' ? 'gemini' : (source === 'codebuddy' ? 'codebuddy' : (source === 'pi' ? 'pi' : 'codex')))),
6983
7283
  keywords: normalizeKeywords(entry.keywords),
6984
7284
  capabilities: normalizeCapabilities(entry.capabilities),
6985
7285
  claudeIndexPath: typeof entry.claudeIndexPath === 'string' ? entry.claudeIndexPath : '',
@@ -7066,7 +7366,13 @@ function purgeExpiredSessionTrashEntries(retentionDays) {
7066
7366
  }
7067
7367
 
7068
7368
  function buildSessionTrashEntry(summary, options = {}) {
7069
- 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')));
7070
7376
  const sessionId = options.sessionId || summary.sessionId || path.basename(options.originalFilePath || summary.filePath || '', '.jsonl');
7071
7377
  const claudeIndexEntry = options.claudeIndexEntry && typeof options.claudeIndexEntry === 'object' && !Array.isArray(options.claudeIndexEntry)
7072
7378
  ? options.claudeIndexEntry
@@ -7074,7 +7380,13 @@ function buildSessionTrashEntry(summary, options = {}) {
7074
7380
  const deletedAt = typeof options.deletedAt === 'string' && options.deletedAt
7075
7381
  ? options.deletedAt
7076
7382
  : new Date().toISOString();
7077
- 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')));
7078
7390
  const fallbackTitle = truncateText(
7079
7391
  (claudeIndexEntry && (claudeIndexEntry.summary || claudeIndexEntry.firstPrompt)) || sessionId,
7080
7392
  120
@@ -7111,7 +7423,7 @@ function buildSessionTrashEntry(summary, options = {}) {
7111
7423
  originalFilePath: options.originalFilePath || summary.filePath || '',
7112
7424
  provider: (claudeIndexEntry && typeof claudeIndexEntry.provider === 'string' && claudeIndexEntry.provider.trim())
7113
7425
  ? claudeIndexEntry.provider.trim()
7114
- : (summary.provider || (source === 'claude' ? 'claude' : 'codex')),
7426
+ : (summary.provider || (source === 'claude' ? 'claude' : (source === 'gemini' ? 'gemini' : (source === 'codebuddy' ? 'codebuddy' : (source === 'pi' ? 'pi' : 'codex'))))),
7115
7427
  keywords: normalizedClaudeKeywords.length > 0 ? normalizedClaudeKeywords : normalizedSummaryKeywords,
7116
7428
  capabilities: Object.keys(normalizedClaudeCapabilities).length > 0
7117
7429
  ? normalizedClaudeCapabilities
@@ -7130,7 +7442,9 @@ function resolveSessionRestoreTarget(entry) {
7130
7442
  ? getClaudeProjectsDir()
7131
7443
  : (normalized.source === 'gemini'
7132
7444
  ? getGeminiTmpDir()
7133
- : (normalized.source === 'codebuddy' ? getCodeBuddyProjectsDir() : getCodexSessionsDir()));
7445
+ : (normalized.source === 'codebuddy'
7446
+ ? getCodeBuddyProjectsDir()
7447
+ : (normalized.source === 'pi' ? getPiSessionsDir() : getCodexSessionsDir())));
7134
7448
  const originalFilePath = typeof normalized.originalFilePath === 'string' ? normalized.originalFilePath.trim() : '';
7135
7449
  if (!root || !originalFilePath) {
7136
7450
  return '';
@@ -7270,7 +7584,9 @@ async function listSessionTrashItems(params = {}) {
7270
7584
  ? 'codex'
7271
7585
  : (params.source === 'gemini'
7272
7586
  ? 'gemini'
7273
- : (params.source === 'codebuddy' ? 'codebuddy' : 'all')));
7587
+ : (params.source === 'codebuddy'
7588
+ ? 'codebuddy'
7589
+ : (params.source === 'pi' ? 'pi' : 'all'))));
7274
7590
  const countOnly = params.countOnly === true;
7275
7591
  const rawLimit = Number(params.limit);
7276
7592
  const limit = Number.isFinite(rawLimit)
@@ -7280,7 +7596,7 @@ async function listSessionTrashItems(params = {}) {
7280
7596
  purgeExpiredSessionTrashEntries(params.retentionDays);
7281
7597
  }
7282
7598
  const allEntries = readSessionTrashEntries();
7283
- let items = source === 'codex' || source === 'claude' || source === 'gemini' || source === 'codebuddy'
7599
+ let items = source === 'codex' || source === 'claude' || source === 'gemini' || source === 'codebuddy' || source === 'pi'
7284
7600
  ? allEntries.filter((entry) => entry.source === source)
7285
7601
  : allEntries.slice();
7286
7602
  items.sort((a, b) => {
@@ -7466,7 +7782,9 @@ async function trashSessionData(params = {}) {
7466
7782
  ? 'codex'
7467
7783
  : (params.source === 'gemini'
7468
7784
  ? 'gemini'
7469
- : (params.source === 'codebuddy' ? 'codebuddy' : '')));
7785
+ : (params.source === 'codebuddy'
7786
+ ? 'codebuddy'
7787
+ : (params.source === 'pi' ? 'pi' : ''))));
7470
7788
  if (!source) {
7471
7789
  return { error: 'Invalid source' };
7472
7790
  }
@@ -7480,7 +7798,9 @@ async function trashSessionData(params = {}) {
7480
7798
  ? parseClaudeSessionSummary(filePath)
7481
7799
  : (source === 'gemini'
7482
7800
  ? parseGeminiSessionSummary(filePath)
7483
- : (source === 'codebuddy' ? parseCodeBuddySessionSummary(filePath) : parseCodexSessionSummary(filePath))))
7801
+ : (source === 'codebuddy'
7802
+ ? parseCodeBuddySessionSummary(filePath)
7803
+ : (source === 'pi' ? parsePiSessionSummary(filePath) : parseCodexSessionSummary(filePath)))))
7484
7804
  || buildSessionSummaryFallback(source, filePath, params.sessionId);
7485
7805
  const exactMessageCount = await countConversationMessagesInFile(filePath, source);
7486
7806
  if (Number.isFinite(Number(exactMessageCount))) {
@@ -7577,7 +7897,9 @@ async function deleteSessionData(params = {}) {
7577
7897
  ? 'codex'
7578
7898
  : (params.source === 'gemini'
7579
7899
  ? 'gemini'
7580
- : (params.source === 'codebuddy' ? 'codebuddy' : '')));
7900
+ : (params.source === 'codebuddy'
7901
+ ? 'codebuddy'
7902
+ : (params.source === 'pi' ? 'pi' : ''))));
7581
7903
  if (!source) {
7582
7904
  return { error: 'Invalid source' };
7583
7905
  }
@@ -8047,6 +8369,32 @@ function extractCodeBuddyMessageFromRecord(record, state, lineIndex = -1) {
8047
8369
  }
8048
8370
  }
8049
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
+
8050
8398
  function recordHasCodexMessage(record) {
8051
8399
  if (!record || record.type !== 'response_item' || !record.payload) {
8052
8400
  return false;
@@ -8088,9 +8436,22 @@ function recordHasCodeBuddyMessage(record) {
8088
8436
  return !!text;
8089
8437
  }
8090
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
+
8091
8451
  function recordHasMessage(record, source) {
8092
8452
  if (source === 'codex') return recordHasCodexMessage(record);
8093
8453
  if (source === 'codebuddy') return recordHasCodeBuddyMessage(record);
8454
+ if (source === 'pi') return recordHasPiMessage(record);
8094
8455
  return recordHasClaudeMessage(record);
8095
8456
  }
8096
8457
 
@@ -8111,6 +8472,8 @@ function extractMessagesFromRecords(records, source, options = {}) {
8111
8472
  extractCodexMessageFromRecord(record, state, lineIndex);
8112
8473
  } else if (source === 'codebuddy') {
8113
8474
  extractCodeBuddyMessageFromRecord(record, state, lineIndex);
8475
+ } else if (source === 'pi') {
8476
+ extractPiMessageFromRecord(record, state, lineIndex);
8114
8477
  } else {
8115
8478
  extractClaudeMessageFromRecord(record, state, lineIndex);
8116
8479
  }
@@ -8174,6 +8537,8 @@ async function extractMessagesFromFile(filePath, source, options = {}) {
8174
8537
  extractCodexMessageFromRecord(record, state, currentLineIndex);
8175
8538
  } else if (source === 'codebuddy') {
8176
8539
  extractCodeBuddyMessageFromRecord(record, state, currentLineIndex);
8540
+ } else if (source === 'pi') {
8541
+ extractPiMessageFromRecord(record, state, currentLineIndex);
8177
8542
  } else {
8178
8543
  extractClaudeMessageFromRecord(record, state, currentLineIndex);
8179
8544
  }
@@ -8204,7 +8569,9 @@ async function readSessionDetail(params = {}) {
8204
8569
  ? 'codex'
8205
8570
  : (params.source === 'gemini'
8206
8571
  ? 'gemini'
8207
- : (params.source === 'codebuddy' ? 'codebuddy' : '')));
8572
+ : (params.source === 'codebuddy'
8573
+ ? 'codebuddy'
8574
+ : (params.source === 'pi' ? 'pi' : ''))));
8208
8575
  if (!source) {
8209
8576
  return { error: 'Invalid source' };
8210
8577
  }
@@ -8266,7 +8633,9 @@ async function readSessionDetail(params = {}) {
8266
8633
  ? 'Codex'
8267
8634
  : (source === 'claude'
8268
8635
  ? 'Claude Code'
8269
- : (source === 'gemini' ? 'Gemini CLI' : 'CodeBuddy Code'));
8636
+ : (source === 'gemini'
8637
+ ? 'Gemini CLI'
8638
+ : (source === 'pi' ? 'Pi' : 'CodeBuddy Code')));
8270
8639
  const clippedMessages = Array.isArray(extracted.messages) ? extracted.messages : [];
8271
8640
  const hasExactTotalMessages = Number.isFinite(extracted.totalMessages);
8272
8641
  const startIndex = hasExactTotalMessages
@@ -8333,7 +8702,9 @@ async function readSessionPlain(params = {}) {
8333
8702
  ? 'codex'
8334
8703
  : (params.source === 'gemini'
8335
8704
  ? 'gemini'
8336
- : (params.source === 'codebuddy' ? 'codebuddy' : '')));
8705
+ : (params.source === 'codebuddy'
8706
+ ? 'codebuddy'
8707
+ : (params.source === 'pi' ? 'pi' : ''))));
8337
8708
  if (!source) {
8338
8709
  return { error: 'Invalid source' };
8339
8710
  }
@@ -8407,7 +8778,9 @@ async function readSessionPlain(params = {}) {
8407
8778
  ? 'Codex'
8408
8779
  : (source === 'claude'
8409
8780
  ? 'Claude Code'
8410
- : (source === 'gemini' ? 'Gemini CLI' : 'CodeBuddy Code'));
8781
+ : (source === 'gemini'
8782
+ ? 'Gemini CLI'
8783
+ : (source === 'pi' ? 'Pi' : 'CodeBuddy Code')));
8411
8784
  const messages = removeLeadingSystemMessage(Array.isArray(extracted.messages) ? extracted.messages : []);
8412
8785
  const text = buildSessionPlainText(messages);
8413
8786
 
@@ -8429,7 +8802,9 @@ async function exportSessionData(params = {}) {
8429
8802
  ? 'codex'
8430
8803
  : (params.source === 'gemini'
8431
8804
  ? 'gemini'
8432
- : (params.source === 'codebuddy' ? 'codebuddy' : '')));
8805
+ : (params.source === 'codebuddy'
8806
+ ? 'codebuddy'
8807
+ : (params.source === 'pi' ? 'pi' : ''))));
8433
8808
  if (!source) {
8434
8809
  return { error: 'Invalid source' };
8435
8810
  }
@@ -8503,7 +8878,9 @@ async function exportSessionData(params = {}) {
8503
8878
  ? 'Codex'
8504
8879
  : (source === 'claude'
8505
8880
  ? 'Claude Code'
8506
- : (source === 'gemini' ? 'Gemini CLI' : 'CodeBuddy Code'));
8881
+ : (source === 'gemini'
8882
+ ? 'Gemini CLI'
8883
+ : (source === 'pi' ? 'Pi' : 'CodeBuddy Code')));
8507
8884
  const truncated = !!extracted.truncated;
8508
8885
  const maxMessagesLabel = maxMessages === Infinity ? 'all' : maxMessages;
8509
8886
  const markdown = buildSessionMarkdown({
@@ -11689,7 +12066,7 @@ function resolveExportOutputPath(outputPath, defaultFileName) {
11689
12066
  }
11690
12067
 
11691
12068
  function printExportSessionUsage() {
11692
- 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>]');
11693
12070
  console.log('\n示例:');
11694
12071
  console.log(' codexmate export-session --source codex --session-id 123456');
11695
12072
  console.log(' codexmate export-session --source claude --file "~/.claude/projects/demo/session.jsonl"');
@@ -11761,8 +12138,8 @@ function parseExportSessionArgs(args = []) {
11761
12138
  }
11762
12139
 
11763
12140
  const normalizedSource = options.source.trim().toLowerCase();
11764
- if (normalizedSource && normalizedSource !== 'codex' && normalizedSource !== 'claude') {
11765
- errors.push('参数 --source 仅支持 codex 或 claude');
12141
+ if (normalizedSource && normalizedSource !== 'codex' && normalizedSource !== 'claude' && normalizedSource !== 'pi') {
12142
+ errors.push('参数 --source 仅支持 codex、claude 或 pi');
11766
12143
  }
11767
12144
  options.source = normalizedSource;
11768
12145
 
@@ -11835,7 +12212,7 @@ async function cmdExportSession(args = []) {
11835
12212
 
11836
12213
  function printAnalyticsUsage() {
11837
12214
  console.log('\n用法:');
11838
- 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|->]');
11839
12216
  console.log('');
11840
12217
  }
11841
12218
 
@@ -13253,8 +13630,8 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
13253
13630
  case 'list-sessions':
13254
13631
  {
13255
13632
  const source = typeof params.source === 'string' ? params.source.trim().toLowerCase() : '';
13256
- if (source && source !== 'codex' && source !== 'claude' && source !== 'gemini' && source !== 'codebuddy' && source !== 'all') {
13257
- 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' };
13258
13635
  } else {
13259
13636
  result = {
13260
13637
  sessions: await listSessionBrowse(params),
@@ -13267,8 +13644,8 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
13267
13644
  {
13268
13645
  const usageParams = isPlainObject(params) ? params : {};
13269
13646
  const source = typeof usageParams.source === 'string' ? usageParams.source.trim().toLowerCase() : '';
13270
- if (source && source !== 'codex' && source !== 'claude' && source !== 'gemini' && source !== 'codebuddy' && source !== 'all') {
13271
- 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' };
13272
13649
  } else {
13273
13650
  result = {
13274
13651
  sessions: await listSessionUsage({
@@ -13284,8 +13661,8 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
13284
13661
  {
13285
13662
  const usageParams = isPlainObject(params) ? params : {};
13286
13663
  const source = typeof usageParams.source === 'string' ? usageParams.source.trim().toLowerCase() : '';
13287
- if (source && source !== 'codex' && source !== 'claude' && source !== 'gemini' && source !== 'codebuddy' && source !== 'all') {
13288
- 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' };
13289
13666
  } else {
13290
13667
  result = await exportSessionUsage({
13291
13668
  ...usageParams,
@@ -13297,8 +13674,8 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
13297
13674
  case 'list-session-paths':
13298
13675
  {
13299
13676
  const source = typeof params.source === 'string' ? params.source.trim().toLowerCase() : '';
13300
- if (source && source !== 'codex' && source !== 'claude' && source !== 'gemini' && source !== 'codebuddy' && source !== 'all') {
13301
- 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' };
13302
13679
  } else {
13303
13680
  result = {
13304
13681
  paths: listSessionPaths(params)
@@ -15557,7 +15934,7 @@ function buildMcpClaudeSettingsPayload() {
15557
15934
  function normalizeMcpSource(value) {
15558
15935
  const source = typeof value === 'string' ? value.trim().toLowerCase() : '';
15559
15936
  if (!source) return '';
15560
- 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') {
15561
15938
  return source;
15562
15939
  }
15563
15940
  return null;
@@ -15870,7 +16247,7 @@ function createWorkflowToolCatalog() {
15870
16247
  handler: async (args = {}) => {
15871
16248
  const source = normalizeMcpSource(args.source);
15872
16249
  if (source === null) {
15873
- 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' };
15874
16251
  }
15875
16252
  return {
15876
16253
  source: source || 'all',
@@ -17990,7 +18367,7 @@ function createMcpTools(options = {}) {
17990
18367
  const input = args && typeof args === 'object' ? args : {};
17991
18368
  const source = normalizeMcpSource(input.source);
17992
18369
  if (source === null) {
17993
- 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' };
17994
18371
  }
17995
18372
  const normalizedInput = {
17996
18373
  ...input,
@@ -18450,7 +18827,7 @@ function createMcpResources() {
18450
18827
  contents: [{
18451
18828
  uri,
18452
18829
  mimeType: 'application/json',
18453
- 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)
18454
18831
  }]
18455
18832
  };
18456
18833
  }
@@ -18700,7 +19077,7 @@ function printMainHelp() {
18700
19077
  console.log(' 注: follow-up 自动排队仅支持 linux/android/netbsd/openbsd/darwin/freebsd 且 stdin 必须是 TTY,其他平台会报错');
18701
19078
  console.log(' codexmate qwen [参数...] 等同于 qwen --yolo');
18702
19079
  console.log(' codexmate mcp [serve] [--transport stdio] [--allow-write|--read-only]');
18703
- 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>]');
18704
19081
  console.log(' codexmate convert-session --from <codex|claude> --to <codex|claude> (--session-id <ID>|--file <PATH>) [--output <PATH>] [--max-messages <N|all|Infinity>]');
18705
19082
  console.log(' codexmate zip <路径> [--max:级别] 压缩(系统 zip 优先,其次 zip-lib)');
18706
19083
  console.log(' codexmate unzip <zip文件> [输出目录] 解压(zip-lib)');