codexmate 0.1.4 → 0.1.6

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();
@@ -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)');