codexmate 0.0.25 → 0.0.27
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 +11 -3
- package/README.zh.md +10 -2
- package/cli/builtin-proxy.js +315 -95
- package/cli/openai-bridge.js +99 -5
- package/cli/session-convert-args.js +65 -0
- package/cli/session-convert-io.js +82 -0
- package/cli/session-convert.js +43 -0
- package/cli.js +547 -32
- package/package.json +74 -74
- package/web-ui/app.js +24 -2
- package/web-ui/logic.session-convert.mjs +70 -0
- package/web-ui/logic.sessions.mjs +151 -0
- package/web-ui/modules/app.computed.dashboard.mjs +44 -1
- package/web-ui/modules/app.computed.session.mjs +336 -12
- package/web-ui/modules/app.methods.claude-config.mjs +11 -1
- package/web-ui/modules/app.methods.codex-config.mjs +76 -0
- package/web-ui/modules/app.methods.navigation.mjs +51 -3
- package/web-ui/modules/app.methods.session-actions.mjs +55 -3
- package/web-ui/modules/app.methods.session-browser.mjs +270 -3
- package/web-ui/modules/app.methods.session-timeline.mjs +34 -3
- package/web-ui/modules/app.methods.session-trash.mjs +16 -1
- package/web-ui/modules/app.methods.startup-claude.mjs +234 -125
- package/web-ui/modules/i18n.dict.mjs +76 -0
- package/web-ui/partials/index/panel-config-claude.html +12 -4
- package/web-ui/partials/index/panel-sessions.html +33 -10
- package/web-ui/partials/index/panel-settings.html +16 -0
- package/web-ui/partials/index/panel-usage.html +95 -85
- package/web-ui/session-helpers.mjs +3 -0
- package/web-ui/styles/base-theme.css +29 -25
- package/web-ui/styles/layout-shell.css +1 -1
- package/web-ui/styles/navigation-panels.css +9 -9
- package/web-ui/styles/sessions-list.css +17 -0
- package/web-ui/styles/sessions-toolbar-trash.css +62 -0
- package/web-ui/styles/sessions-usage.css +211 -83
- package/web-ui/styles/settings-panel.css +19 -0
package/cli.js
CHANGED
|
@@ -115,6 +115,7 @@ const {
|
|
|
115
115
|
const {
|
|
116
116
|
createZipCommandController
|
|
117
117
|
} = require('./cli/zip-commands');
|
|
118
|
+
const { cmdConvertSession } = require('./cli/session-convert');
|
|
118
119
|
const {
|
|
119
120
|
getCodexSkillsDir,
|
|
120
121
|
getClaudeSkillsDir,
|
|
@@ -196,6 +197,11 @@ const CLAUDE_MD_FILE_NAME = 'CLAUDE.md';
|
|
|
196
197
|
const CLAUDE_PROJECTS_DIR = path.join(os.homedir(), '.claude', 'projects');
|
|
197
198
|
const CODEBUDDY_DIR = path.join(os.homedir(), '.codebuddy');
|
|
198
199
|
const CODEBUDDY_PROJECTS_DIR = path.join(CODEBUDDY_DIR, 'projects');
|
|
200
|
+
const CODEXMATE_DIR = path.join(os.homedir(), '.codexmate');
|
|
201
|
+
const CODEXMATE_SESSIONS_DIR = path.join(CODEXMATE_DIR, 'sessions');
|
|
202
|
+
const CODEXMATE_DERIVED_SESSIONS_DIR = path.join(CODEXMATE_SESSIONS_DIR, 'derived');
|
|
203
|
+
const CODEXMATE_DERIVED_CODEX_DIR = path.join(CODEXMATE_DERIVED_SESSIONS_DIR, 'codex');
|
|
204
|
+
const CODEXMATE_DERIVED_CLAUDE_DIR = path.join(CODEXMATE_DERIVED_SESSIONS_DIR, 'claude');
|
|
199
205
|
const GEMINI_DIR = path.join(os.homedir(), '.gemini');
|
|
200
206
|
const GEMINI_TMP_DIR = path.join(GEMINI_DIR, 'tmp');
|
|
201
207
|
const RECENT_CONFIGS_FILE = path.join(CONFIG_DIR, 'recent-configs.json');
|
|
@@ -216,6 +222,7 @@ const DEFAULT_MODELS = ['gpt-5.3-codex', 'gpt-5.1-codex-max', 'gpt-4-turbo', 'gp
|
|
|
216
222
|
const SPEED_TEST_TIMEOUT_MS = 8000;
|
|
217
223
|
const MAX_SESSION_LIST_SIZE = 300;
|
|
218
224
|
const MAX_SESSION_TRASH_LIST_SIZE = 500;
|
|
225
|
+
const DEFAULT_SESSION_TRASH_RETENTION_DAYS = 30;
|
|
219
226
|
const MAX_EXPORT_MESSAGES = 1000;
|
|
220
227
|
const DEFAULT_SESSION_DETAIL_MESSAGES = 300;
|
|
221
228
|
const MAX_SESSION_DETAIL_MESSAGES = 1000;
|
|
@@ -1308,6 +1315,58 @@ function getCodeBuddyProjectsDir() {
|
|
|
1308
1315
|
return resolveExistingDir(candidates, CODEBUDDY_PROJECTS_DIR);
|
|
1309
1316
|
}
|
|
1310
1317
|
|
|
1318
|
+
function getCodexmateDerivedSessionsRoot(target) {
|
|
1319
|
+
if (target === 'claude') {
|
|
1320
|
+
return CODEXMATE_DERIVED_CLAUDE_DIR;
|
|
1321
|
+
}
|
|
1322
|
+
return CODEXMATE_DERIVED_CODEX_DIR;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
function normalizeSessionDerivedTarget(value) {
|
|
1326
|
+
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
1327
|
+
if (normalized === 'codex' || normalized === 'claude') {
|
|
1328
|
+
return normalized;
|
|
1329
|
+
}
|
|
1330
|
+
return '';
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
function normalizeSessionDerivedSource(value) {
|
|
1334
|
+
return normalizeSessionDerivedTarget(value);
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
function buildSessionDerivedSourceKey(source, sessionId, filePath) {
|
|
1338
|
+
const baseSource = normalizeSessionDerivedSource(source);
|
|
1339
|
+
const id = typeof sessionId === 'string' ? sessionId.trim() : '';
|
|
1340
|
+
const pathValue = typeof filePath === 'string' ? filePath.trim() : '';
|
|
1341
|
+
const seed = `${baseSource}|${id}|${pathValue}`;
|
|
1342
|
+
return crypto.createHash('sha1').update(seed).digest('hex').slice(0, 16);
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
function formatCompactTimestamp(value = Date.now()) {
|
|
1346
|
+
const stamp = new Date(value);
|
|
1347
|
+
const year = String(stamp.getFullYear());
|
|
1348
|
+
const month = String(stamp.getMonth() + 1).padStart(2, '0');
|
|
1349
|
+
const day = String(stamp.getDate()).padStart(2, '0');
|
|
1350
|
+
const hour = String(stamp.getHours()).padStart(2, '0');
|
|
1351
|
+
const minute = String(stamp.getMinutes()).padStart(2, '0');
|
|
1352
|
+
const second = String(stamp.getSeconds()).padStart(2, '0');
|
|
1353
|
+
return `${year}${month}${day}-${hour}${minute}${second}`;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
function buildDerivedSessionId(baseId) {
|
|
1357
|
+
const safeBase = typeof baseId === 'string' && baseId.trim() ? baseId.trim() : 'session';
|
|
1358
|
+
const normalized = safeBase.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 64) || 'session';
|
|
1359
|
+
const suffix = crypto.randomBytes(3).toString('hex');
|
|
1360
|
+
return `${normalized}-${formatCompactTimestamp()}-${suffix}`;
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
function buildDerivedSessionOutputDir(target, source, sourceKey) {
|
|
1364
|
+
const targetRoot = getCodexmateDerivedSessionsRoot(target);
|
|
1365
|
+
const safeSource = normalizeSessionDerivedSource(source) || 'codex';
|
|
1366
|
+
const safeKey = typeof sourceKey === 'string' && sourceKey.trim() ? sourceKey.trim() : 'unknown';
|
|
1367
|
+
return path.join(targetRoot, safeSource, safeKey);
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1311
1370
|
function readModelsCacheEntry(cacheKey) {
|
|
1312
1371
|
if (!cacheKey) return null;
|
|
1313
1372
|
const entry = g_modelsCache.get(cacheKey);
|
|
@@ -2869,6 +2928,46 @@ async function hydrateSessionItemsExactMessageCount(items) {
|
|
|
2869
2928
|
});
|
|
2870
2929
|
}
|
|
2871
2930
|
|
|
2931
|
+
function getSessionExportKeyForApi(item) {
|
|
2932
|
+
const source = item && item.source ? String(item.source).trim() : '';
|
|
2933
|
+
const sessionId = item && item.sessionId ? String(item.sessionId) : '';
|
|
2934
|
+
const filePath = item && item.filePath ? String(item.filePath) : '';
|
|
2935
|
+
return `${source || 'unknown'}:${sessionId}:${filePath}`;
|
|
2936
|
+
}
|
|
2937
|
+
|
|
2938
|
+
async function readSessionMessageCounts(params = {}) {
|
|
2939
|
+
const rawItems = Array.isArray(params.items) ? params.items : [];
|
|
2940
|
+
const rawLimit = Number(params.limit);
|
|
2941
|
+
const limit = Number.isFinite(rawLimit) ? Math.max(1, Math.min(Math.floor(rawLimit), 80)) : 40;
|
|
2942
|
+
const items = rawItems.slice(0, limit);
|
|
2943
|
+
const hydrated = await mapWithConcurrency(items, 4, async (item) => {
|
|
2944
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
2945
|
+
return undefined;
|
|
2946
|
+
}
|
|
2947
|
+
const key = getSessionExportKeyForApi(item);
|
|
2948
|
+
const source = item.source === 'claude'
|
|
2949
|
+
? 'claude'
|
|
2950
|
+
: (item.source === 'codex'
|
|
2951
|
+
? 'codex'
|
|
2952
|
+
: (item.source === 'gemini' ? 'gemini' : (item.source === 'codebuddy' ? 'codebuddy' : '')));
|
|
2953
|
+
const filePath = typeof item.filePath === 'string' ? item.filePath : '';
|
|
2954
|
+
if (!source || !filePath || !fs.existsSync(filePath)) {
|
|
2955
|
+
return { key };
|
|
2956
|
+
}
|
|
2957
|
+
const exactMessageCount = await countConversationMessagesInFile(filePath, source);
|
|
2958
|
+
if (!Number.isFinite(Number(exactMessageCount))) {
|
|
2959
|
+
return { key };
|
|
2960
|
+
}
|
|
2961
|
+
return {
|
|
2962
|
+
key,
|
|
2963
|
+
messageCount: Math.max(0, Math.floor(Number(exactMessageCount)))
|
|
2964
|
+
};
|
|
2965
|
+
});
|
|
2966
|
+
return {
|
|
2967
|
+
items: hydrated.filter(Boolean)
|
|
2968
|
+
};
|
|
2969
|
+
}
|
|
2970
|
+
|
|
2872
2971
|
function sortSessionsByUpdatedAt(items) {
|
|
2873
2972
|
items.sort((a, b) => {
|
|
2874
2973
|
const aTime = Date.parse(a.updatedAt || '') || 0;
|
|
@@ -3279,6 +3378,57 @@ function collectRecentJsonlFiles(rootDir, options = {}) {
|
|
|
3279
3378
|
return filesMeta.slice(0, returnCount).map(item => item.filePath);
|
|
3280
3379
|
}
|
|
3281
3380
|
|
|
3381
|
+
function collectRecentJsonlFilesFromRoots(rootDirs, options = {}) {
|
|
3382
|
+
const roots = Array.isArray(rootDirs)
|
|
3383
|
+
? rootDirs.filter((dirPath) => typeof dirPath === 'string' && dirPath.trim() && fs.existsSync(dirPath.trim()))
|
|
3384
|
+
: [];
|
|
3385
|
+
if (roots.length === 0) {
|
|
3386
|
+
return [];
|
|
3387
|
+
}
|
|
3388
|
+
|
|
3389
|
+
const returnCount = Math.max(1, Number(options.returnCount) || 1);
|
|
3390
|
+
const maxFilesScanned = Math.max(returnCount, Number(options.maxFilesScanned) || 2000);
|
|
3391
|
+
const ignoreSubPath = typeof options.ignoreSubPath === 'string' ? options.ignoreSubPath : '';
|
|
3392
|
+
const stack = roots.map((dirPath) => dirPath.trim());
|
|
3393
|
+
const filesMeta = [];
|
|
3394
|
+
let scanned = 0;
|
|
3395
|
+
|
|
3396
|
+
while (stack.length > 0 && scanned < maxFilesScanned) {
|
|
3397
|
+
const dir = stack.pop();
|
|
3398
|
+
let entries = [];
|
|
3399
|
+
try {
|
|
3400
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
3401
|
+
} catch (_) {
|
|
3402
|
+
continue;
|
|
3403
|
+
}
|
|
3404
|
+
|
|
3405
|
+
for (const entry of entries) {
|
|
3406
|
+
const fullPath = path.join(dir, entry.name);
|
|
3407
|
+
if (entry.isDirectory()) {
|
|
3408
|
+
stack.push(fullPath);
|
|
3409
|
+
continue;
|
|
3410
|
+
}
|
|
3411
|
+
if (!entry.isFile() || !entry.name.endsWith('.jsonl')) {
|
|
3412
|
+
continue;
|
|
3413
|
+
}
|
|
3414
|
+
if (ignoreSubPath && fullPath.includes(ignoreSubPath)) {
|
|
3415
|
+
continue;
|
|
3416
|
+
}
|
|
3417
|
+
scanned += 1;
|
|
3418
|
+
try {
|
|
3419
|
+
const stat = fs.statSync(fullPath);
|
|
3420
|
+
filesMeta.push({ filePath: fullPath, mtimeMs: stat.mtimeMs || 0 });
|
|
3421
|
+
} catch (_) {}
|
|
3422
|
+
if (scanned >= maxFilesScanned) {
|
|
3423
|
+
break;
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
|
|
3428
|
+
filesMeta.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
3429
|
+
return filesMeta.slice(0, returnCount).map(item => item.filePath);
|
|
3430
|
+
}
|
|
3431
|
+
|
|
3282
3432
|
function getSessionListCache(cacheKey, forceRefresh = false) {
|
|
3283
3433
|
if (forceRefresh) {
|
|
3284
3434
|
g_sessionListCache.delete(cacheKey);
|
|
@@ -4394,7 +4544,7 @@ function listCodexSessions(limit, options = {}) {
|
|
|
4394
4544
|
const titleReadBytes = Number.isFinite(Number(options.titleReadBytes))
|
|
4395
4545
|
? Math.max(1024, Math.floor(Number(options.titleReadBytes)))
|
|
4396
4546
|
: SESSION_TITLE_READ_BYTES;
|
|
4397
|
-
const files =
|
|
4547
|
+
const files = collectRecentJsonlFilesFromRoots([codexSessionsDir, getCodexmateDerivedSessionsRoot('codex')], {
|
|
4398
4548
|
returnCount: scanCount,
|
|
4399
4549
|
maxFilesScanned
|
|
4400
4550
|
});
|
|
@@ -4406,7 +4556,10 @@ function listCodexSessions(limit, options = {}) {
|
|
|
4406
4556
|
titleReadBytes
|
|
4407
4557
|
});
|
|
4408
4558
|
if (summary) {
|
|
4409
|
-
sessions.push(
|
|
4559
|
+
sessions.push({
|
|
4560
|
+
...summary,
|
|
4561
|
+
derived: isDerivedSessionFile(filePath)
|
|
4562
|
+
});
|
|
4410
4563
|
}
|
|
4411
4564
|
|
|
4412
4565
|
if (sessions.length >= targetCount) {
|
|
@@ -4419,7 +4572,10 @@ function listCodexSessions(limit, options = {}) {
|
|
|
4419
4572
|
|
|
4420
4573
|
function listClaudeSessions(limit, options = {}) {
|
|
4421
4574
|
const claudeProjectsDir = getClaudeProjectsDir();
|
|
4422
|
-
|
|
4575
|
+
const derivedClaudeRoot = getCodexmateDerivedSessionsRoot('claude');
|
|
4576
|
+
const hasProjectsDir = fs.existsSync(claudeProjectsDir);
|
|
4577
|
+
const hasDerivedDir = fs.existsSync(derivedClaudeRoot);
|
|
4578
|
+
if (!hasProjectsDir && !hasDerivedDir) {
|
|
4423
4579
|
return [];
|
|
4424
4580
|
}
|
|
4425
4581
|
|
|
@@ -4447,12 +4603,14 @@ function listClaudeSessions(limit, options = {}) {
|
|
|
4447
4603
|
|
|
4448
4604
|
const sessions = [];
|
|
4449
4605
|
let projectDirs = [];
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
.
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4606
|
+
if (hasProjectsDir) {
|
|
4607
|
+
try {
|
|
4608
|
+
projectDirs = fs.readdirSync(claudeProjectsDir, { withFileTypes: true })
|
|
4609
|
+
.filter(entry => entry.isDirectory())
|
|
4610
|
+
.map(entry => path.join(claudeProjectsDir, entry.name));
|
|
4611
|
+
} catch (e) {
|
|
4612
|
+
projectDirs = [];
|
|
4613
|
+
}
|
|
4456
4614
|
}
|
|
4457
4615
|
|
|
4458
4616
|
for (const projectDir of projectDirs) {
|
|
@@ -4583,6 +4741,7 @@ function listClaudeSessions(limit, options = {}) {
|
|
|
4583
4741
|
models,
|
|
4584
4742
|
__messageCountExact: quickRecords.length > 0 && isSessionSummaryMessageCountExact(fileStat, summaryReadBytes),
|
|
4585
4743
|
filePath,
|
|
4744
|
+
derived: isDerivedSessionFile(filePath),
|
|
4586
4745
|
keywords,
|
|
4587
4746
|
capabilities
|
|
4588
4747
|
});
|
|
@@ -4609,7 +4768,10 @@ function listClaudeSessions(limit, options = {}) {
|
|
|
4609
4768
|
titleReadBytes
|
|
4610
4769
|
});
|
|
4611
4770
|
if (summary) {
|
|
4612
|
-
sessions.push(
|
|
4771
|
+
sessions.push({
|
|
4772
|
+
...summary,
|
|
4773
|
+
derived: isDerivedSessionFile(filePath)
|
|
4774
|
+
});
|
|
4613
4775
|
}
|
|
4614
4776
|
|
|
4615
4777
|
if (sessions.length >= targetCount) {
|
|
@@ -4618,6 +4780,28 @@ function listClaudeSessions(limit, options = {}) {
|
|
|
4618
4780
|
}
|
|
4619
4781
|
}
|
|
4620
4782
|
|
|
4783
|
+
if (fs.existsSync(derivedClaudeRoot)) {
|
|
4784
|
+
const seen = new Set(sessions.map((item) => (item && item.filePath ? item.filePath : '')).filter(Boolean));
|
|
4785
|
+
const derivedFiles = collectRecentJsonlFiles(derivedClaudeRoot, {
|
|
4786
|
+
returnCount: scanCount,
|
|
4787
|
+
maxFilesScanned
|
|
4788
|
+
});
|
|
4789
|
+
for (const filePath of derivedFiles) {
|
|
4790
|
+
if (seen.has(filePath)) continue;
|
|
4791
|
+
const summary = parseClaudeSessionSummary(filePath, {
|
|
4792
|
+
summaryReadBytes,
|
|
4793
|
+
titleReadBytes
|
|
4794
|
+
});
|
|
4795
|
+
if (summary) {
|
|
4796
|
+
sessions.push({
|
|
4797
|
+
...summary,
|
|
4798
|
+
derived: isDerivedSessionFile(filePath)
|
|
4799
|
+
});
|
|
4800
|
+
}
|
|
4801
|
+
seen.add(filePath);
|
|
4802
|
+
}
|
|
4803
|
+
}
|
|
4804
|
+
|
|
4621
4805
|
return mergeAndLimitSessions(sessions, limit);
|
|
4622
4806
|
}
|
|
4623
4807
|
|
|
@@ -4962,19 +5146,25 @@ function resolveSessionFilePath(source, filePath, sessionId) {
|
|
|
4962
5146
|
const normalizedSource = source === 'claude' || source === 'gemini' || source === 'codebuddy'
|
|
4963
5147
|
? source
|
|
4964
5148
|
: 'codex';
|
|
4965
|
-
const
|
|
4966
|
-
|
|
5149
|
+
const homeDir = process && process.env && process.env.HOME ? process.env.HOME : '';
|
|
5150
|
+
const derivedCodexDir = homeDir ? `${homeDir}/.codexmate/sessions/derived/codex` : '';
|
|
5151
|
+
const derivedClaudeDir = homeDir ? `${homeDir}/.codexmate/sessions/derived/claude` : '';
|
|
5152
|
+
const roots = normalizedSource === 'claude'
|
|
5153
|
+
? [getClaudeProjectsDir(), derivedClaudeDir]
|
|
4967
5154
|
: (normalizedSource === 'gemini'
|
|
4968
|
-
? getGeminiTmpDir()
|
|
4969
|
-
: (normalizedSource === 'codebuddy'
|
|
4970
|
-
|
|
5155
|
+
? [getGeminiTmpDir()]
|
|
5156
|
+
: (normalizedSource === 'codebuddy'
|
|
5157
|
+
? [getCodeBuddyProjectsDir()]
|
|
5158
|
+
: [getCodexSessionsDir(), derivedCodexDir]));
|
|
5159
|
+
const availableRoots = roots.filter((dirPath) => dirPath && fs.existsSync(dirPath));
|
|
5160
|
+
if (availableRoots.length === 0) {
|
|
4971
5161
|
return '';
|
|
4972
5162
|
}
|
|
4973
5163
|
|
|
4974
5164
|
if (typeof filePath === 'string' && filePath.trim()) {
|
|
4975
5165
|
const expandedPath = expandHomePath(filePath.trim());
|
|
4976
5166
|
const targetPath = expandedPath ? path.resolve(expandedPath) : '';
|
|
4977
|
-
if (targetPath && fs.existsSync(targetPath) && isPathInside(targetPath,
|
|
5167
|
+
if (targetPath && fs.existsSync(targetPath) && availableRoots.some((rootPath) => isPathInside(targetPath, rootPath))) {
|
|
4978
5168
|
return targetPath;
|
|
4979
5169
|
}
|
|
4980
5170
|
}
|
|
@@ -4984,7 +5174,7 @@ function resolveSessionFilePath(source, filePath, sessionId) {
|
|
|
4984
5174
|
const lookupStore = g_sessionFileLookupCache[normalizedSource];
|
|
4985
5175
|
if (lookupStore instanceof Map && lookupStore.has(targetId)) {
|
|
4986
5176
|
const cachedPath = lookupStore.get(targetId);
|
|
4987
|
-
if (cachedPath && fs.existsSync(cachedPath) && isPathInside(cachedPath,
|
|
5177
|
+
if (cachedPath && fs.existsSync(cachedPath) && availableRoots.some((rootPath) => isPathInside(cachedPath, rootPath))) {
|
|
4988
5178
|
return cachedPath;
|
|
4989
5179
|
}
|
|
4990
5180
|
lookupStore.delete(targetId);
|
|
@@ -5019,7 +5209,11 @@ function resolveSessionFilePath(source, filePath, sessionId) {
|
|
|
5019
5209
|
}
|
|
5020
5210
|
matchedFile = filesMeta.find(item => path.basename(item, '.json').toLowerCase() === targetId) || '';
|
|
5021
5211
|
} else {
|
|
5022
|
-
const files =
|
|
5212
|
+
const files = [];
|
|
5213
|
+
for (const rootPath of availableRoots) {
|
|
5214
|
+
files.push(...collectJsonlFiles(rootPath, 5000));
|
|
5215
|
+
if (files.length >= 5000) break;
|
|
5216
|
+
}
|
|
5023
5217
|
matchedFile = files.find(item => path.basename(item, '.jsonl').toLowerCase() === targetId) || '';
|
|
5024
5218
|
}
|
|
5025
5219
|
if (matchedFile && fs.existsSync(matchedFile)) {
|
|
@@ -5067,6 +5261,65 @@ function findClaudeSessionIndexPath(sessionFilePath) {
|
|
|
5067
5261
|
return '';
|
|
5068
5262
|
}
|
|
5069
5263
|
|
|
5264
|
+
function resolveClaudeProjectDirForCwd(cwd) {
|
|
5265
|
+
const projectsDir = getClaudeProjectsDir();
|
|
5266
|
+
const raw = typeof cwd === 'string' ? cwd.trim() : '';
|
|
5267
|
+
if (!projectsDir || !raw) {
|
|
5268
|
+
return '';
|
|
5269
|
+
}
|
|
5270
|
+
const ignoreCase = process.platform === 'win32';
|
|
5271
|
+
const resolvedCwd = path.resolve(expandHomePath(raw));
|
|
5272
|
+
let entries = [];
|
|
5273
|
+
try {
|
|
5274
|
+
entries = fs.readdirSync(projectsDir, { withFileTypes: true });
|
|
5275
|
+
} catch (_) {
|
|
5276
|
+
entries = [];
|
|
5277
|
+
}
|
|
5278
|
+
for (const entry of entries) {
|
|
5279
|
+
if (!entry || !entry.isDirectory()) continue;
|
|
5280
|
+
const projectDir = path.join(projectsDir, entry.name);
|
|
5281
|
+
const indexPath = path.join(projectDir, 'sessions-index.json');
|
|
5282
|
+
if (!fs.existsSync(indexPath)) continue;
|
|
5283
|
+
const index = readJsonFile(indexPath, null);
|
|
5284
|
+
const originalPathRaw = index && typeof index.originalPath === 'string' ? index.originalPath.trim() : '';
|
|
5285
|
+
if (!originalPathRaw) continue;
|
|
5286
|
+
const resolvedOriginal = path.resolve(expandHomePath(originalPathRaw));
|
|
5287
|
+
if (normalizePathForCompare(resolvedOriginal, { ignoreCase }) === normalizePathForCompare(resolvedCwd, { ignoreCase })) {
|
|
5288
|
+
return projectDir;
|
|
5289
|
+
}
|
|
5290
|
+
}
|
|
5291
|
+
const hash = crypto.createHash('sha1').update(resolvedCwd).digest('hex').slice(0, 12);
|
|
5292
|
+
return path.join(projectsDir, `codexmate-${hash}`);
|
|
5293
|
+
}
|
|
5294
|
+
|
|
5295
|
+
function ensureClaudeSessionsIndex(indexPath, originalPath) {
|
|
5296
|
+
if (!indexPath) return;
|
|
5297
|
+
const resolvedOriginal = typeof originalPath === 'string' && originalPath.trim()
|
|
5298
|
+
? path.resolve(expandHomePath(originalPath.trim()))
|
|
5299
|
+
: '';
|
|
5300
|
+
const existing = readJsonFile(indexPath, null);
|
|
5301
|
+
const index = existing && typeof existing === 'object' && !Array.isArray(existing)
|
|
5302
|
+
? { ...existing }
|
|
5303
|
+
: { entries: [] };
|
|
5304
|
+
if (!Array.isArray(index.entries)) {
|
|
5305
|
+
index.entries = [];
|
|
5306
|
+
}
|
|
5307
|
+
if (!index.originalPath && resolvedOriginal) {
|
|
5308
|
+
index.originalPath = resolvedOriginal;
|
|
5309
|
+
}
|
|
5310
|
+
if (!fs.existsSync(indexPath)) {
|
|
5311
|
+
if (!index.originalPath) {
|
|
5312
|
+
index.originalPath = resolvedOriginal || path.dirname(indexPath);
|
|
5313
|
+
}
|
|
5314
|
+
writeJsonAtomic(indexPath, index);
|
|
5315
|
+
return;
|
|
5316
|
+
}
|
|
5317
|
+
if (existing && typeof existing === 'object' && !Array.isArray(existing) && existing.originalPath === index.originalPath) {
|
|
5318
|
+
return;
|
|
5319
|
+
}
|
|
5320
|
+
writeJsonAtomic(indexPath, index);
|
|
5321
|
+
}
|
|
5322
|
+
|
|
5070
5323
|
const {
|
|
5071
5324
|
findAvailablePort,
|
|
5072
5325
|
saveBuiltinProxySettings,
|
|
@@ -5349,6 +5602,35 @@ function readSessionTrashEntries(options = {}) {
|
|
|
5349
5602
|
return normalizedEntries;
|
|
5350
5603
|
}
|
|
5351
5604
|
|
|
5605
|
+
function purgeExpiredSessionTrashEntries(retentionDays) {
|
|
5606
|
+
const days = Number.isFinite(Number(retentionDays)) && Number(retentionDays) > 0
|
|
5607
|
+
? Math.floor(Number(retentionDays))
|
|
5608
|
+
: DEFAULT_SESSION_TRASH_RETENTION_DAYS;
|
|
5609
|
+
const cutoffMs = Date.now() - days * 24 * 60 * 60 * 1000;
|
|
5610
|
+
const entries = readSessionTrashEntries({ cleanup: false });
|
|
5611
|
+
if (entries.length === 0) {
|
|
5612
|
+
return { purged: 0 };
|
|
5613
|
+
}
|
|
5614
|
+
const remaining = [];
|
|
5615
|
+
let purgedCount = 0;
|
|
5616
|
+
for (const entry of entries) {
|
|
5617
|
+
const deletedAtMs = Date.parse(entry.deletedAt || entry.updatedAt || '') || 0;
|
|
5618
|
+
if (deletedAtMs > 0 && deletedAtMs < cutoffMs) {
|
|
5619
|
+
const trashFilePath = resolveSessionTrashFilePath(entry);
|
|
5620
|
+
if (trashFilePath) {
|
|
5621
|
+
try { fs.unlinkSync(trashFilePath); } catch (_) {}
|
|
5622
|
+
}
|
|
5623
|
+
purgedCount += 1;
|
|
5624
|
+
} else {
|
|
5625
|
+
remaining.push(entry);
|
|
5626
|
+
}
|
|
5627
|
+
}
|
|
5628
|
+
if (purgedCount > 0) {
|
|
5629
|
+
writeSessionTrashEntries(remaining);
|
|
5630
|
+
}
|
|
5631
|
+
return { purged: purgedCount };
|
|
5632
|
+
}
|
|
5633
|
+
|
|
5352
5634
|
function buildSessionTrashEntry(summary, options = {}) {
|
|
5353
5635
|
const source = options.source === 'claude' ? 'claude' : 'codex';
|
|
5354
5636
|
const sessionId = options.sessionId || summary.sessionId || path.basename(options.originalFilePath || summary.filePath || '', '.jsonl');
|
|
@@ -5560,6 +5842,9 @@ async function listSessionTrashItems(params = {}) {
|
|
|
5560
5842
|
const limit = Number.isFinite(rawLimit)
|
|
5561
5843
|
? Math.max(1, Math.min(rawLimit, MAX_SESSION_TRASH_LIST_SIZE))
|
|
5562
5844
|
: 200;
|
|
5845
|
+
if (params.autoPurge !== false) {
|
|
5846
|
+
purgeExpiredSessionTrashEntries(params.retentionDays);
|
|
5847
|
+
}
|
|
5563
5848
|
const allEntries = readSessionTrashEntries();
|
|
5564
5849
|
let items = source === 'codex' || source === 'claude' || source === 'gemini' || source === 'codebuddy'
|
|
5565
5850
|
? allEntries.filter((entry) => entry.source === source)
|
|
@@ -6122,6 +6407,28 @@ function buildSessionPlainText(messages) {
|
|
|
6122
6407
|
return lines.join('\n');
|
|
6123
6408
|
}
|
|
6124
6409
|
|
|
6410
|
+
function getDerivedSessionMetaPath(filePath) {
|
|
6411
|
+
if (!filePath) return '';
|
|
6412
|
+
const base = filePath.toLowerCase().endsWith('.jsonl')
|
|
6413
|
+
? filePath.slice(0, -5)
|
|
6414
|
+
: filePath;
|
|
6415
|
+
return `${base}.meta.json`;
|
|
6416
|
+
}
|
|
6417
|
+
|
|
6418
|
+
function isDerivedSessionFile(filePath) {
|
|
6419
|
+
const metaPath = getDerivedSessionMetaPath(filePath);
|
|
6420
|
+
if (!metaPath) return false;
|
|
6421
|
+
try {
|
|
6422
|
+
if (fs.existsSync(metaPath)) {
|
|
6423
|
+
return true;
|
|
6424
|
+
}
|
|
6425
|
+
} catch (_) {
|
|
6426
|
+
return false;
|
|
6427
|
+
}
|
|
6428
|
+
const base = path.basename(filePath || '', path.extname(filePath || ''));
|
|
6429
|
+
return /-\d{8}-\d{6}-[0-9a-f]{6}$/i.test(base);
|
|
6430
|
+
}
|
|
6431
|
+
|
|
6125
6432
|
function resolveStateMaxMessages(state) {
|
|
6126
6433
|
if (!state || typeof state !== 'object') {
|
|
6127
6434
|
return MAX_EXPORT_MESSAGES;
|
|
@@ -6467,6 +6774,20 @@ async function readSessionDetail(params = {}) {
|
|
|
6467
6774
|
sessionId,
|
|
6468
6775
|
cwd: extracted.cwd || '',
|
|
6469
6776
|
updatedAt: extracted.updatedAt || '',
|
|
6777
|
+
derived: (() => {
|
|
6778
|
+
try {
|
|
6779
|
+
const metaPath = filePath.toLowerCase().endsWith('.jsonl')
|
|
6780
|
+
? `${filePath.slice(0, -5)}.meta.json`
|
|
6781
|
+
: `${filePath}.meta.json`;
|
|
6782
|
+
if (fs.existsSync(metaPath)) {
|
|
6783
|
+
return true;
|
|
6784
|
+
}
|
|
6785
|
+
} catch (_) {
|
|
6786
|
+
return false;
|
|
6787
|
+
}
|
|
6788
|
+
const base = path.basename(filePath || '', path.extname(filePath || ''));
|
|
6789
|
+
return /-\d{8}-\d{6}-[0-9a-f]{6}$/i.test(base);
|
|
6790
|
+
})(),
|
|
6470
6791
|
totalMessages: hasExactTotalMessages ? extracted.totalMessages : null,
|
|
6471
6792
|
clipped: typeof extracted.clipped === 'boolean'
|
|
6472
6793
|
? extracted.clipped
|
|
@@ -6494,6 +6815,15 @@ async function readSessionPlain(params = {}) {
|
|
|
6494
6815
|
return { error: 'Session file not found' };
|
|
6495
6816
|
}
|
|
6496
6817
|
|
|
6818
|
+
const rawMaxMessages = params.maxMessages;
|
|
6819
|
+
const maxMessages = rawMaxMessages === Infinity || rawMaxMessages === 'all'
|
|
6820
|
+
? Infinity
|
|
6821
|
+
: (
|
|
6822
|
+
Number.isFinite(Number(rawMaxMessages))
|
|
6823
|
+
? Math.max(1, Math.floor(Number(rawMaxMessages)))
|
|
6824
|
+
: 50
|
|
6825
|
+
);
|
|
6826
|
+
|
|
6497
6827
|
let extracted;
|
|
6498
6828
|
if (source === 'gemini') {
|
|
6499
6829
|
let json;
|
|
@@ -6514,15 +6844,19 @@ async function readSessionPlain(params = {}) {
|
|
|
6514
6844
|
const text = extractMessageText(extractGeminiMessageText(entry.content ?? entry.message ?? entry.text));
|
|
6515
6845
|
if (!text && role !== 'system') continue;
|
|
6516
6846
|
messages.push({ role, text });
|
|
6847
|
+
if (maxMessages !== Infinity && messages.length >= maxMessages) {
|
|
6848
|
+
break;
|
|
6849
|
+
}
|
|
6517
6850
|
}
|
|
6518
6851
|
extracted = {
|
|
6519
6852
|
sessionId: typeof json.sessionId === 'string' && json.sessionId.trim() ? json.sessionId.trim() : path.basename(filePath, '.json'),
|
|
6520
6853
|
cwd: typeof json.projectRoot === 'string' ? json.projectRoot : '',
|
|
6521
|
-
messages
|
|
6854
|
+
messages,
|
|
6855
|
+
truncated: maxMessages !== Infinity && rawMessages.length > messages.length
|
|
6522
6856
|
};
|
|
6523
6857
|
} else {
|
|
6524
6858
|
try {
|
|
6525
|
-
extracted = await extractMessagesFromFile(filePath, source, { maxMessages
|
|
6859
|
+
extracted = await extractMessagesFromFile(filePath, source, { maxMessages });
|
|
6526
6860
|
} catch (e) {
|
|
6527
6861
|
extracted = null;
|
|
6528
6862
|
}
|
|
@@ -6536,7 +6870,7 @@ async function readSessionPlain(params = {}) {
|
|
|
6536
6870
|
if (fallbackRecords.length === 0) {
|
|
6537
6871
|
return { error: 'Session file is empty' };
|
|
6538
6872
|
}
|
|
6539
|
-
extracted = extractMessagesFromRecords(fallbackRecords, source, { maxMessages
|
|
6873
|
+
extracted = extractMessagesFromRecords(fallbackRecords, source, { maxMessages });
|
|
6540
6874
|
}
|
|
6541
6875
|
}
|
|
6542
6876
|
|
|
@@ -6555,7 +6889,8 @@ async function readSessionPlain(params = {}) {
|
|
|
6555
6889
|
sessionId,
|
|
6556
6890
|
title: sessionId,
|
|
6557
6891
|
filePath,
|
|
6558
|
-
text
|
|
6892
|
+
text,
|
|
6893
|
+
clipped: maxMessages !== Infinity && !!(extracted && extracted.truncated)
|
|
6559
6894
|
};
|
|
6560
6895
|
}
|
|
6561
6896
|
|
|
@@ -6663,6 +6998,163 @@ async function exportSessionData(params = {}) {
|
|
|
6663
6998
|
};
|
|
6664
6999
|
}
|
|
6665
7000
|
|
|
7001
|
+
async function convertSessionToDerived(params = {}) {
|
|
7002
|
+
const source = normalizeSessionDerivedSource(params.source);
|
|
7003
|
+
const target = normalizeSessionDerivedTarget(params.target || params.to);
|
|
7004
|
+
if (!source || !target) {
|
|
7005
|
+
return { error: 'Invalid source/target' };
|
|
7006
|
+
}
|
|
7007
|
+
if (source === target) {
|
|
7008
|
+
return { error: 'source and target must be different' };
|
|
7009
|
+
}
|
|
7010
|
+
|
|
7011
|
+
const maxMessages = resolveMaxMessagesValue(params.maxMessages, MAX_EXPORT_MESSAGES);
|
|
7012
|
+
const filePath = resolveSessionFilePath(source, getSessionFileArg(params), params.sessionId);
|
|
7013
|
+
if (!filePath) {
|
|
7014
|
+
return { error: 'Session file not found' };
|
|
7015
|
+
}
|
|
7016
|
+
|
|
7017
|
+
let extracted;
|
|
7018
|
+
try {
|
|
7019
|
+
extracted = await extractMessagesFromFile(filePath, source, { maxMessages });
|
|
7020
|
+
} catch (_) {
|
|
7021
|
+
extracted = null;
|
|
7022
|
+
}
|
|
7023
|
+
if (!extracted) {
|
|
7024
|
+
return { error: 'Failed to parse session file' };
|
|
7025
|
+
}
|
|
7026
|
+
|
|
7027
|
+
const baseSessionId = extracted.sessionId || params.sessionId || path.basename(filePath, '.jsonl');
|
|
7028
|
+
const derivedSessionId = buildDerivedSessionId(baseSessionId);
|
|
7029
|
+
const sourceKey = buildSessionDerivedSourceKey(source, baseSessionId, filePath);
|
|
7030
|
+
const outputDir = target === 'codex'
|
|
7031
|
+
? getCodexSessionsDir()
|
|
7032
|
+
: (target === 'claude'
|
|
7033
|
+
? (resolveClaudeProjectDirForCwd(extracted.cwd || '') || path.join(getClaudeProjectsDir(), 'codexmate-derived'))
|
|
7034
|
+
: buildDerivedSessionOutputDir(target, source, sourceKey));
|
|
7035
|
+
ensureDir(outputDir);
|
|
7036
|
+
const outputPath = path.join(outputDir, `${derivedSessionId}.jsonl`);
|
|
7037
|
+
const metaPath = path.join(outputDir, `${derivedSessionId}.meta.json`);
|
|
7038
|
+
|
|
7039
|
+
const cwd = typeof extracted.cwd === 'string' ? extracted.cwd : '';
|
|
7040
|
+
const resolvedCwd = cwd ? path.resolve(expandHomePath(cwd)) : '';
|
|
7041
|
+
const messages = removeLeadingSystemMessage(Array.isArray(extracted.messages) ? extracted.messages : []);
|
|
7042
|
+
const now = Date.now();
|
|
7043
|
+
const baseTime = new Date(now).toISOString();
|
|
7044
|
+
const lines = [];
|
|
7045
|
+
|
|
7046
|
+
if (target === 'codex') {
|
|
7047
|
+
lines.push(JSON.stringify({ type: 'session_meta', timestamp: baseTime, payload: { id: derivedSessionId, cwd } }));
|
|
7048
|
+
for (let i = 0; i < messages.length; i += 1) {
|
|
7049
|
+
const message = messages[i];
|
|
7050
|
+
if (!message) continue;
|
|
7051
|
+
const role = normalizeRole(message.role);
|
|
7052
|
+
if (role !== 'user' && role !== 'assistant' && role !== 'system') continue;
|
|
7053
|
+
const text = typeof message.text === 'string' ? message.text : '';
|
|
7054
|
+
if (!text) continue;
|
|
7055
|
+
lines.push(JSON.stringify({
|
|
7056
|
+
type: 'response_item',
|
|
7057
|
+
timestamp: toIsoTime(message.timestamp, '') || new Date(now + i).toISOString(),
|
|
7058
|
+
payload: { type: 'message', role, content: text }
|
|
7059
|
+
}));
|
|
7060
|
+
}
|
|
7061
|
+
} else {
|
|
7062
|
+
const claudeIndexPath = target === 'claude' ? path.join(outputDir, 'sessions-index.json') : '';
|
|
7063
|
+
for (let i = 0; i < messages.length; i += 1) {
|
|
7064
|
+
const message = messages[i];
|
|
7065
|
+
if (!message) continue;
|
|
7066
|
+
const role = normalizeRole(message.role);
|
|
7067
|
+
if (role !== 'user' && role !== 'assistant' && role !== 'system') continue;
|
|
7068
|
+
const text = typeof message.text === 'string' ? message.text : '';
|
|
7069
|
+
if (!text) continue;
|
|
7070
|
+
lines.push(JSON.stringify({
|
|
7071
|
+
type: role,
|
|
7072
|
+
timestamp: toIsoTime(message.timestamp, '') || new Date(now + i).toISOString(),
|
|
7073
|
+
sessionId: derivedSessionId,
|
|
7074
|
+
cwd,
|
|
7075
|
+
message: { content: text }
|
|
7076
|
+
}));
|
|
7077
|
+
}
|
|
7078
|
+
if (claudeIndexPath) {
|
|
7079
|
+
ensureClaudeSessionsIndex(claudeIndexPath, resolvedCwd);
|
|
7080
|
+
}
|
|
7081
|
+
}
|
|
7082
|
+
|
|
7083
|
+
fs.writeFileSync(outputPath, `${lines.join('\n')}\n`, 'utf-8');
|
|
7084
|
+
writeJsonAtomic(metaPath, {
|
|
7085
|
+
version: 1,
|
|
7086
|
+
createdAt: baseTime,
|
|
7087
|
+
source: {
|
|
7088
|
+
type: source,
|
|
7089
|
+
sessionId: baseSessionId,
|
|
7090
|
+
filePath
|
|
7091
|
+
},
|
|
7092
|
+
target: {
|
|
7093
|
+
type: target,
|
|
7094
|
+
sessionId: derivedSessionId,
|
|
7095
|
+
filePath: outputPath
|
|
7096
|
+
},
|
|
7097
|
+
options: {
|
|
7098
|
+
maxMessages: maxMessages === Infinity ? 'all' : maxMessages
|
|
7099
|
+
}
|
|
7100
|
+
});
|
|
7101
|
+
|
|
7102
|
+
invalidateSessionListCache();
|
|
7103
|
+
|
|
7104
|
+
const summary = target === 'codex'
|
|
7105
|
+
? parseCodexSessionSummary(outputPath, { summaryReadBytes: SESSION_BROWSE_SUMMARY_READ_BYTES, titleReadBytes: SESSION_BROWSE_SUMMARY_READ_BYTES })
|
|
7106
|
+
: parseClaudeSessionSummary(outputPath, { summaryReadBytes: SESSION_BROWSE_SUMMARY_READ_BYTES, titleReadBytes: SESSION_BROWSE_SUMMARY_READ_BYTES });
|
|
7107
|
+
if (target === 'claude' && summary) {
|
|
7108
|
+
const indexPath = path.join(outputDir, 'sessions-index.json');
|
|
7109
|
+
ensureClaudeSessionsIndex(indexPath, resolvedCwd);
|
|
7110
|
+
upsertClaudeSessionIndexEntry(indexPath, outputPath, {
|
|
7111
|
+
source: 'claude',
|
|
7112
|
+
trashId: summary.sessionId,
|
|
7113
|
+
trashFileName: `${summary.sessionId}.jsonl`,
|
|
7114
|
+
sessionId: summary.sessionId,
|
|
7115
|
+
title: summary.title,
|
|
7116
|
+
cwd: summary.cwd,
|
|
7117
|
+
createdAt: summary.createdAt,
|
|
7118
|
+
updatedAt: summary.updatedAt,
|
|
7119
|
+
messageCount: summary.messageCount,
|
|
7120
|
+
provider: summary.provider,
|
|
7121
|
+
keywords: summary.keywords,
|
|
7122
|
+
capabilities: summary.capabilities,
|
|
7123
|
+
claudeIndexEntry: resolvedCwd ? { projectPath: resolvedCwd } : null
|
|
7124
|
+
});
|
|
7125
|
+
}
|
|
7126
|
+
const maxMessagesLabel = maxMessages === Infinity ? 'all' : maxMessages;
|
|
7127
|
+
|
|
7128
|
+
return {
|
|
7129
|
+
derived: true,
|
|
7130
|
+
source,
|
|
7131
|
+
target,
|
|
7132
|
+
truncated: !!extracted.truncated,
|
|
7133
|
+
maxMessages: maxMessagesLabel,
|
|
7134
|
+
session: summary ? { ...summary, derived: true } : {
|
|
7135
|
+
source: target,
|
|
7136
|
+
sourceLabel: target === 'codex' ? 'Codex' : 'Claude Code',
|
|
7137
|
+
sessionId: derivedSessionId,
|
|
7138
|
+
title: derivedSessionId,
|
|
7139
|
+
cwd,
|
|
7140
|
+
createdAt: baseTime,
|
|
7141
|
+
updatedAt: baseTime,
|
|
7142
|
+
messageCount: messages.length,
|
|
7143
|
+
totalTokens: 0,
|
|
7144
|
+
contextWindow: 0,
|
|
7145
|
+
inputTokens: 0,
|
|
7146
|
+
cachedInputTokens: 0,
|
|
7147
|
+
outputTokens: 0,
|
|
7148
|
+
reasoningOutputTokens: 0,
|
|
7149
|
+
__messageCountExact: true,
|
|
7150
|
+
filePath: outputPath,
|
|
7151
|
+
derived: true,
|
|
7152
|
+
keywords: [],
|
|
7153
|
+
capabilities: {}
|
|
7154
|
+
}
|
|
7155
|
+
};
|
|
7156
|
+
}
|
|
7157
|
+
|
|
6666
7158
|
function buildExportPayload(includeKeys) {
|
|
6667
7159
|
const { config } = readConfigOrVirtualDefault();
|
|
6668
7160
|
const providers = config.model_providers || {};
|
|
@@ -8294,17 +8786,32 @@ function probeCliBinary(binName) {
|
|
|
8294
8786
|
let lastError = '';
|
|
8295
8787
|
|
|
8296
8788
|
for (const args of attempts) {
|
|
8297
|
-
const argString = args.join(' ').trim();
|
|
8298
|
-
const commandLine = argString ? `${binName} ${argString}` : binName;
|
|
8299
8789
|
try {
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
|
|
8790
|
+
let output = '';
|
|
8791
|
+
let status = 0;
|
|
8792
|
+
if (process.platform === 'win32') {
|
|
8793
|
+
const argString = args.join(' ').trim();
|
|
8794
|
+
const commandLine = argString ? `${binName} ${argString}` : binName;
|
|
8795
|
+
const stdout = execSync(commandLine, {
|
|
8796
|
+
encoding: 'utf8',
|
|
8797
|
+
windowsHide: true,
|
|
8798
|
+
timeout: 5000,
|
|
8799
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
8800
|
+
shell: true
|
|
8801
|
+
});
|
|
8802
|
+
output = String(stdout || '');
|
|
8803
|
+
} else {
|
|
8804
|
+
const cmd = resolveSpawnCommand(binName);
|
|
8805
|
+
const probe = spawnSync(cmd, args, {
|
|
8806
|
+
encoding: 'utf8',
|
|
8807
|
+
windowsHide: true,
|
|
8808
|
+
timeout: 5000,
|
|
8809
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
8810
|
+
});
|
|
8811
|
+
status = Number.isFinite(probe.status) ? probe.status : (probe.error ? 1 : 0);
|
|
8812
|
+
output = `${probe.stdout || ''}\n${probe.stderr || ''}`.trim();
|
|
8813
|
+
}
|
|
8814
|
+
const version = parseBinaryVersionOutput(output);
|
|
8308
8815
|
return {
|
|
8309
8816
|
installed: true,
|
|
8310
8817
|
bin: binName,
|
|
@@ -9831,12 +10338,18 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
9831
10338
|
case 'export-session':
|
|
9832
10339
|
result = await exportSessionData(params);
|
|
9833
10340
|
break;
|
|
10341
|
+
case 'convert-session':
|
|
10342
|
+
result = await convertSessionToDerived(params || {});
|
|
10343
|
+
break;
|
|
9834
10344
|
case 'delete-session':
|
|
9835
10345
|
result = await deleteSessionData(params || {});
|
|
9836
10346
|
break;
|
|
9837
10347
|
case 'clone-session':
|
|
9838
10348
|
result = await cloneCodexSession(params || {});
|
|
9839
10349
|
break;
|
|
10350
|
+
case 'session-message-counts':
|
|
10351
|
+
result = await readSessionMessageCounts(params || {});
|
|
10352
|
+
break;
|
|
9840
10353
|
case 'session-detail':
|
|
9841
10354
|
result = await readSessionDetail(params);
|
|
9842
10355
|
break;
|
|
@@ -14614,6 +15127,7 @@ function printMainHelp() {
|
|
|
14614
15127
|
console.log(' codexmate qwen [参数...] 等同于 qwen --yolo');
|
|
14615
15128
|
console.log(' codexmate mcp [serve] [--transport stdio] [--allow-write|--read-only]');
|
|
14616
15129
|
console.log(' codexmate export-session --source <codex|claude|gemini|codebuddy> (--session-id <ID>|--file <PATH>) [--output <PATH>] [--max-messages <N|all|Infinity>]');
|
|
15130
|
+
console.log(' codexmate convert-session --from <codex|claude> --to <codex|claude> (--session-id <ID>|--file <PATH>) [--output <PATH>] [--max-messages <N|all|Infinity>]');
|
|
14617
15131
|
console.log(' codexmate zip <路径> [--max:级别] 压缩(系统 zip 优先,其次 zip-lib)');
|
|
14618
15132
|
console.log(' codexmate unzip <zip文件> [输出目录] 解压(zip-lib)');
|
|
14619
15133
|
console.log(' codexmate unzip-ext <zip目录> [输出目录] [--ext:后缀[,后缀...]] [--no-recursive] 批量提取 ZIP 指定后缀文件(默认递归)');
|
|
@@ -14712,6 +15226,7 @@ async function main() {
|
|
|
14712
15226
|
}
|
|
14713
15227
|
case 'mcp': await cmdMcp(args.slice(1)); break;
|
|
14714
15228
|
case 'export-session': await cmdExportSession(args.slice(1)); break;
|
|
15229
|
+
case 'convert-session': await cmdConvertSession(args.slice(1), { resolveSessionFilePath }); break;
|
|
14715
15230
|
case 'zip': {
|
|
14716
15231
|
const { targetPath, options } = parseZipCommandArgs(args.slice(1));
|
|
14717
15232
|
await cmdZip(targetPath, options);
|