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/README.md +2 -2
- package/README.vi.md +2 -2
- package/README.zh.md +2 -2
- package/cli/analytics-export-args.js +2 -2
- package/cli/session-usage.js +5 -2
- package/cli.js +434 -57
- package/lib/cli-sessions.js +19 -0
- package/package.json +1 -1
- package/web-ui/app.js +2 -2
- package/web-ui/logic.sessions.mjs +5 -5
- package/web-ui/modules/app.computed.dashboard.mjs +0 -4
- package/web-ui/modules/app.computed.session.mjs +2 -1
- package/web-ui/modules/app.methods.session-actions.mjs +12 -4
- package/web-ui/modules/app.methods.session-browser.mjs +8 -4
- package/web-ui/modules/app.methods.session-trash.mjs +2 -2
- package/web-ui/modules/app.methods.startup-claude.mjs +2 -1
- package/web-ui/modules/app.methods.tool-config-permissions.mjs +3 -2
- package/web-ui/modules/app.methods.web-ui-preferences.mjs +2 -1
- package/web-ui/modules/i18n/locales/en.mjs +6 -8
- package/web-ui/modules/i18n/locales/ja.mjs +6 -8
- package/web-ui/modules/i18n/locales/vi.mjs +6 -8
- package/web-ui/modules/i18n/locales/zh-tw.mjs +6 -8
- package/web-ui/modules/i18n/locales/zh.mjs +6 -8
- package/web-ui/partials/index/panel-config-openclaw.html +37 -8
- package/web-ui/partials/index/panel-docs.html +0 -26
- package/web-ui/res/web-ui-render.precompiled.js +175 -163
- package/web-ui/styles/docs-panel.css +75 -90
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'
|
|
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'
|
|
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'
|
|
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'
|
|
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
|
-
:
|
|
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'
|
|
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
|
-
:
|
|
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'
|
|
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'
|
|
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'
|
|
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'
|
|
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'
|
|
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'
|
|
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'
|
|
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'
|
|
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'
|
|
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'
|
|
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'
|
|
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'
|
|
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'
|
|
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'
|
|
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'
|
|
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'
|
|
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'
|
|
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 或
|
|
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)');
|