codexmate 0.0.30 → 0.0.31
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 +18 -76
- package/README.zh.md +18 -1
- package/cli/session-convert-args.js +5 -1
- package/cli/session-convert.js +111 -4
- package/cli.js +329 -32
- package/package.json +1 -1
- package/web-ui/app.js +1 -0
- package/web-ui/modules/app.methods.codex-config.mjs +0 -76
- package/web-ui/modules/app.methods.session-actions.mjs +16 -18
- package/web-ui/modules/i18n.dict.mjs +30 -2
- package/web-ui/partials/index/panel-config-codex.html +31 -11
- package/web-ui/partials/index/panel-config-codex.html.bak +337 -0
- package/web-ui/partials/index/panel-sessions.html +4 -10
- package/web-ui/session-helpers.mjs +0 -3
- package/web-ui/styles/bridge-pool.css +197 -0
- package/web-ui/styles/responsive.css +3 -3
- package/web-ui/styles/sessions-list.css +2 -2
- package/web-ui/styles/sessions-toolbar-trash.css +14 -0
- package/web-ui/styles.css +1 -0
package/cli.js
CHANGED
|
@@ -1388,7 +1388,10 @@ function formatCompactTimestamp(value = Date.now()) {
|
|
|
1388
1388
|
return `${year}${month}${day}-${hour}${minute}${second}`;
|
|
1389
1389
|
}
|
|
1390
1390
|
|
|
1391
|
-
function buildDerivedSessionId(baseId) {
|
|
1391
|
+
function buildDerivedSessionId(baseId, useUuid) {
|
|
1392
|
+
if (useUuid) {
|
|
1393
|
+
return crypto.randomUUID();
|
|
1394
|
+
}
|
|
1392
1395
|
const safeBase = typeof baseId === 'string' && baseId.trim() ? baseId.trim() : 'session';
|
|
1393
1396
|
const normalized = safeBase.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 64) || 'session';
|
|
1394
1397
|
const suffix = crypto.randomBytes(3).toString('hex');
|
|
@@ -3026,7 +3029,7 @@ function mergeAndLimitSessions(items, limit) {
|
|
|
3026
3029
|
const seen = new Set();
|
|
3027
3030
|
for (const item of items) {
|
|
3028
3031
|
if (!item || !item.filePath) continue;
|
|
3029
|
-
const key = `${item.source}:${item.filePath}`;
|
|
3032
|
+
const key = `${item.source}:${item.sessionId || item.filePath}`;
|
|
3030
3033
|
if (seen.has(key)) continue;
|
|
3031
3034
|
seen.add(key);
|
|
3032
3035
|
deduped.push(item);
|
|
@@ -4627,10 +4630,10 @@ function listCodexSessions(limit, options = {}) {
|
|
|
4627
4630
|
titleReadBytes
|
|
4628
4631
|
});
|
|
4629
4632
|
if (summary) {
|
|
4630
|
-
sessions.push({
|
|
4633
|
+
sessions.push(attachSessionNativeStatus({
|
|
4631
4634
|
...summary,
|
|
4632
4635
|
derived: isDerivedSessionFile(filePath)
|
|
4633
|
-
});
|
|
4636
|
+
}));
|
|
4634
4637
|
}
|
|
4635
4638
|
|
|
4636
4639
|
if (sessions.length >= targetCount) {
|
|
@@ -4796,7 +4799,7 @@ function listClaudeSessions(limit, options = {}) {
|
|
|
4796
4799
|
const keywords = normalizeKeywords(entry.keywords);
|
|
4797
4800
|
const capabilities = normalizeCapabilities(entry.capabilities);
|
|
4798
4801
|
|
|
4799
|
-
sessions.push({
|
|
4802
|
+
sessions.push(attachSessionNativeStatus({
|
|
4800
4803
|
source: 'claude',
|
|
4801
4804
|
sourceLabel: 'Claude Code',
|
|
4802
4805
|
provider,
|
|
@@ -4820,7 +4823,7 @@ function listClaudeSessions(limit, options = {}) {
|
|
|
4820
4823
|
derived: isDerivedSessionFile(filePath),
|
|
4821
4824
|
keywords,
|
|
4822
4825
|
capabilities
|
|
4823
|
-
});
|
|
4826
|
+
}));
|
|
4824
4827
|
|
|
4825
4828
|
if (sessions.length >= targetCount) {
|
|
4826
4829
|
break;
|
|
@@ -4844,10 +4847,10 @@ function listClaudeSessions(limit, options = {}) {
|
|
|
4844
4847
|
titleReadBytes
|
|
4845
4848
|
});
|
|
4846
4849
|
if (summary) {
|
|
4847
|
-
sessions.push({
|
|
4850
|
+
sessions.push(attachSessionNativeStatus({
|
|
4848
4851
|
...summary,
|
|
4849
4852
|
derived: isDerivedSessionFile(filePath)
|
|
4850
|
-
});
|
|
4853
|
+
}));
|
|
4851
4854
|
}
|
|
4852
4855
|
|
|
4853
4856
|
if (sessions.length >= targetCount) {
|
|
@@ -4869,10 +4872,10 @@ function listClaudeSessions(limit, options = {}) {
|
|
|
4869
4872
|
titleReadBytes
|
|
4870
4873
|
});
|
|
4871
4874
|
if (summary) {
|
|
4872
|
-
sessions.push({
|
|
4875
|
+
sessions.push(attachSessionNativeStatus({
|
|
4873
4876
|
...summary,
|
|
4874
4877
|
derived: isDerivedSessionFile(filePath)
|
|
4875
|
-
});
|
|
4878
|
+
}));
|
|
4876
4879
|
}
|
|
4877
4880
|
seen.add(filePath);
|
|
4878
4881
|
}
|
|
@@ -6580,7 +6583,7 @@ function buildSessionPlainText(messages) {
|
|
|
6580
6583
|
function getDerivedSessionMetaPath(filePath) {
|
|
6581
6584
|
if (!filePath) return '';
|
|
6582
6585
|
const base = filePath.toLowerCase().endsWith('.jsonl')
|
|
6583
|
-
? filePath.slice(0, -
|
|
6586
|
+
? filePath.slice(0, -6)
|
|
6584
6587
|
: filePath;
|
|
6585
6588
|
return `${base}.meta.json`;
|
|
6586
6589
|
}
|
|
@@ -6596,7 +6599,88 @@ function isDerivedSessionFile(filePath) {
|
|
|
6596
6599
|
return false;
|
|
6597
6600
|
}
|
|
6598
6601
|
const base = path.basename(filePath || '', path.extname(filePath || ''));
|
|
6599
|
-
|
|
6602
|
+
if (/-\d{8}-\d{6}-[0-9a-f]{6}$/i.test(base)) return true;
|
|
6603
|
+
const norm = (filePath || '').replace(/\\/g, '/');
|
|
6604
|
+
if (norm.includes('/.codexmate/sessions/derived/')) return true;
|
|
6605
|
+
return false;
|
|
6606
|
+
}
|
|
6607
|
+
|
|
6608
|
+
function readDerivedSessionMeta(filePath) {
|
|
6609
|
+
const metaPath = getDerivedSessionMetaPath(filePath);
|
|
6610
|
+
if (!metaPath) return null;
|
|
6611
|
+
return readJsonFile(metaPath, null);
|
|
6612
|
+
}
|
|
6613
|
+
|
|
6614
|
+
function resolveNativeSessionFilePath(source, sessionId, cwd = '') {
|
|
6615
|
+
const normalizedSource = normalizeSessionDerivedTarget(source);
|
|
6616
|
+
const id = typeof sessionId === 'string' ? sessionId.trim() : '';
|
|
6617
|
+
if (!normalizedSource || !id) return '';
|
|
6618
|
+
if (normalizedSource === 'codex') {
|
|
6619
|
+
return path.join(getCodexSessionsDir(), `${id}.jsonl`);
|
|
6620
|
+
}
|
|
6621
|
+
const projectDir = resolveClaudeProjectDirForCwd(cwd || '') || path.join(getClaudeProjectsDir(), 'codexmate-derived');
|
|
6622
|
+
return path.join(projectDir, `${id}.jsonl`);
|
|
6623
|
+
}
|
|
6624
|
+
|
|
6625
|
+
function buildSessionNativeStatus(source, sessionId, cwd, filePath, derived) {
|
|
6626
|
+
const normalizedSource = normalizeSessionDerivedTarget(source);
|
|
6627
|
+
const id = typeof sessionId === 'string' ? sessionId.trim() : '';
|
|
6628
|
+
const currentPath = typeof filePath === 'string' && filePath.trim()
|
|
6629
|
+
? path.resolve(expandHomePath(filePath.trim()))
|
|
6630
|
+
: '';
|
|
6631
|
+
const nativePath = resolveNativeSessionFilePath(normalizedSource, id, cwd || '');
|
|
6632
|
+
const resolvedNativePath = nativePath ? path.resolve(expandHomePath(nativePath)) : '';
|
|
6633
|
+
const inNativePath = !!(currentPath && resolvedNativePath && currentPath === resolvedNativePath);
|
|
6634
|
+
const nativeExists = !!(resolvedNativePath && fs.existsSync(resolvedNativePath));
|
|
6635
|
+
const currentExists = !!(currentPath && fs.existsSync(currentPath));
|
|
6636
|
+
const isDerived = derived === true || isDerivedSessionFile(currentPath);
|
|
6637
|
+
const nativeAvailable = inNativePath || nativeExists || (!isDerived && currentExists);
|
|
6638
|
+
const effectiveNativePath = !isDerived && currentPath
|
|
6639
|
+
? currentPath
|
|
6640
|
+
: (resolvedNativePath || '');
|
|
6641
|
+
return {
|
|
6642
|
+
derived: isDerived,
|
|
6643
|
+
nativeAvailable,
|
|
6644
|
+
nativePath: effectiveNativePath,
|
|
6645
|
+
derivedPath: isDerived && currentPath && !inNativePath ? currentPath : '',
|
|
6646
|
+
nativeImportAvailable: isDerived && !!resolvedNativePath && !inNativePath
|
|
6647
|
+
};
|
|
6648
|
+
}
|
|
6649
|
+
|
|
6650
|
+
function attachSessionNativeStatus(session) {
|
|
6651
|
+
if (!session || typeof session !== 'object') return session;
|
|
6652
|
+
const meta = session.meta && typeof session.meta === 'object' && !Array.isArray(session.meta)
|
|
6653
|
+
? session.meta
|
|
6654
|
+
: (session.derived === true ? readDerivedSessionMeta(session.filePath) : null);
|
|
6655
|
+
const metaConvertedFromLabel = buildConvertedFromLabel(meta);
|
|
6656
|
+
const convertedFromLabel = typeof session.convertedFromLabel === 'string' && session.convertedFromLabel.trim()
|
|
6657
|
+
? session.convertedFromLabel.trim()
|
|
6658
|
+
: metaConvertedFromLabel;
|
|
6659
|
+
const convertedFrom = typeof session.convertedFrom === 'string' && session.convertedFrom.trim()
|
|
6660
|
+
? session.convertedFrom.trim()
|
|
6661
|
+
: (convertedFromLabel ? convertedFromLabel.toLowerCase().replace(' code', '') : '');
|
|
6662
|
+
return {
|
|
6663
|
+
...session,
|
|
6664
|
+
...(convertedFrom ? { convertedFrom } : {}),
|
|
6665
|
+
...(convertedFromLabel ? { convertedFromLabel } : {}),
|
|
6666
|
+
...buildSessionNativeStatus(
|
|
6667
|
+
session.source,
|
|
6668
|
+
session.sessionId,
|
|
6669
|
+
session.cwd,
|
|
6670
|
+
session.filePath,
|
|
6671
|
+
session.derived === true
|
|
6672
|
+
)
|
|
6673
|
+
};
|
|
6674
|
+
}
|
|
6675
|
+
|
|
6676
|
+
function buildConvertedFromLabel(meta) {
|
|
6677
|
+
const rawSourceType = meta && meta.source && typeof meta.source.type === 'string'
|
|
6678
|
+
? meta.source.type
|
|
6679
|
+
: (meta && typeof meta.convertedFrom === 'string' ? meta.convertedFrom : '');
|
|
6680
|
+
const sourceType = rawSourceType.trim().toLowerCase();
|
|
6681
|
+
if (sourceType === 'codex') return 'Codex';
|
|
6682
|
+
if (sourceType === 'claude') return 'Claude Code';
|
|
6683
|
+
return '';
|
|
6600
6684
|
}
|
|
6601
6685
|
|
|
6602
6686
|
function resolveStateMaxMessages(state) {
|
|
@@ -6947,7 +7031,7 @@ async function readSessionDetail(params = {}) {
|
|
|
6947
7031
|
derived: (() => {
|
|
6948
7032
|
try {
|
|
6949
7033
|
const metaPath = filePath.toLowerCase().endsWith('.jsonl')
|
|
6950
|
-
? `${filePath.slice(0, -
|
|
7034
|
+
? `${filePath.slice(0, -6)}.meta.json`
|
|
6951
7035
|
: `${filePath}.meta.json`;
|
|
6952
7036
|
if (fs.existsSync(metaPath)) {
|
|
6953
7037
|
return true;
|
|
@@ -6956,7 +7040,9 @@ async function readSessionDetail(params = {}) {
|
|
|
6956
7040
|
return false;
|
|
6957
7041
|
}
|
|
6958
7042
|
const base = path.basename(filePath || '', path.extname(filePath || ''));
|
|
6959
|
-
|
|
7043
|
+
if (/-\d{8}-\d{6}-[0-9a-f]{6}$/i.test(base)) return true;
|
|
7044
|
+
const norm = (filePath || '').replace(/\\/g, '/');
|
|
7045
|
+
return norm.includes('/.codexmate/sessions/derived/');
|
|
6960
7046
|
})(),
|
|
6961
7047
|
totalMessages: hasExactTotalMessages ? extracted.totalMessages : null,
|
|
6962
7048
|
clipped: typeof extracted.clipped === 'boolean'
|
|
@@ -6964,7 +7050,18 @@ async function readSessionDetail(params = {}) {
|
|
|
6964
7050
|
: (hasExactTotalMessages ? extracted.totalMessages > indexedMessages.length : false),
|
|
6965
7051
|
messageLimit,
|
|
6966
7052
|
messages: indexedMessages,
|
|
6967
|
-
filePath
|
|
7053
|
+
filePath,
|
|
7054
|
+
...(typeof buildSessionNativeStatus === 'function'
|
|
7055
|
+
? buildSessionNativeStatus(source, sessionId, extracted.cwd || '', filePath, (typeof isDerivedSessionFile === 'function' ? isDerivedSessionFile(filePath) : false))
|
|
7056
|
+
: { derived: (typeof isDerivedSessionFile === 'function' ? isDerivedSessionFile(filePath) : false) }),
|
|
7057
|
+
convertedFrom: (() => {
|
|
7058
|
+
if (typeof buildConvertedFromLabel !== 'function' || typeof readDerivedSessionMeta !== 'function') return '';
|
|
7059
|
+
const label = buildConvertedFromLabel(readDerivedSessionMeta(filePath));
|
|
7060
|
+
return label ? label.toLowerCase().replace(' code', '') : '';
|
|
7061
|
+
})(),
|
|
7062
|
+
convertedFromLabel: (typeof buildConvertedFromLabel === 'function' && typeof readDerivedSessionMeta === 'function')
|
|
7063
|
+
? buildConvertedFromLabel(readDerivedSessionMeta(filePath))
|
|
7064
|
+
: ''
|
|
6968
7065
|
};
|
|
6969
7066
|
}
|
|
6970
7067
|
|
|
@@ -7195,18 +7292,45 @@ async function convertSessionToDerived(params = {}) {
|
|
|
7195
7292
|
}
|
|
7196
7293
|
|
|
7197
7294
|
const baseSessionId = extracted.sessionId || params.sessionId || path.basename(filePath, '.jsonl');
|
|
7198
|
-
const derivedSessionId = buildDerivedSessionId(baseSessionId);
|
|
7199
7295
|
const sourceKey = buildSessionDerivedSourceKey(source, baseSessionId, filePath);
|
|
7200
|
-
const
|
|
7201
|
-
?
|
|
7202
|
-
: (
|
|
7203
|
-
|
|
7204
|
-
|
|
7296
|
+
const outputDirModeRaw = typeof params.outputDir === 'string'
|
|
7297
|
+
? params.outputDir
|
|
7298
|
+
: (typeof params.output_dir === 'string' ? params.output_dir : 'native');
|
|
7299
|
+
const outputDirMode = outputDirModeRaw.trim().toLowerCase() === 'derived' ? 'derived' : 'native';
|
|
7300
|
+
const cwd = typeof extracted.cwd === 'string' ? extracted.cwd : '';
|
|
7301
|
+
const outputDir = outputDirMode === 'derived'
|
|
7302
|
+
? buildDerivedSessionOutputDir(target, source, sourceKey)
|
|
7303
|
+
: (target === 'codex'
|
|
7304
|
+
? getCodexSessionsDir()
|
|
7305
|
+
: (resolveClaudeProjectDirForCwd(cwd || '') || path.join(getClaudeProjectsDir(), 'codexmate-derived')));
|
|
7205
7306
|
ensureDir(outputDir);
|
|
7206
|
-
const outputPath = path.join(outputDir, `${derivedSessionId}.jsonl`);
|
|
7207
|
-
const metaPath = path.join(outputDir, `${derivedSessionId}.meta.json`);
|
|
7208
7307
|
|
|
7209
|
-
|
|
7308
|
+
let derivedSessionId = '';
|
|
7309
|
+
let outputPath = '';
|
|
7310
|
+
let metaPath = '';
|
|
7311
|
+
if (outputDirMode === 'native') {
|
|
7312
|
+
derivedSessionId = baseSessionId;
|
|
7313
|
+
outputPath = path.join(outputDir, `${derivedSessionId}.jsonl`);
|
|
7314
|
+
metaPath = path.join(outputDir, `${derivedSessionId}.meta.json`);
|
|
7315
|
+
if (fs.existsSync(outputPath) || fs.existsSync(metaPath)) {
|
|
7316
|
+
return { error: 'Converted sessionId conflicts with an existing native session; please retry or choose derived output.' };
|
|
7317
|
+
}
|
|
7318
|
+
} else {
|
|
7319
|
+
const useUuid = target === 'codex' || target === 'claude';
|
|
7320
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
7321
|
+
derivedSessionId = buildDerivedSessionId(baseSessionId, useUuid);
|
|
7322
|
+
outputPath = path.join(outputDir, `${derivedSessionId}.jsonl`);
|
|
7323
|
+
metaPath = path.join(outputDir, `${derivedSessionId}.meta.json`);
|
|
7324
|
+
if (!fs.existsSync(outputPath) && !fs.existsSync(metaPath)) {
|
|
7325
|
+
break;
|
|
7326
|
+
}
|
|
7327
|
+
derivedSessionId = '';
|
|
7328
|
+
}
|
|
7329
|
+
if (!derivedSessionId) {
|
|
7330
|
+
return { error: 'Converted sessionId conflicts with an existing native session; please retry or choose derived output.' };
|
|
7331
|
+
}
|
|
7332
|
+
}
|
|
7333
|
+
|
|
7210
7334
|
const resolvedCwd = cwd ? path.resolve(expandHomePath(cwd)) : '';
|
|
7211
7335
|
const messages = removeLeadingSystemMessage(Array.isArray(extracted.messages) ? extracted.messages : []);
|
|
7212
7336
|
const now = Date.now();
|
|
@@ -7265,7 +7389,8 @@ async function convertSessionToDerived(params = {}) {
|
|
|
7265
7389
|
filePath: outputPath
|
|
7266
7390
|
},
|
|
7267
7391
|
options: {
|
|
7268
|
-
maxMessages: maxMessages === Infinity ? 'all' : maxMessages
|
|
7392
|
+
maxMessages: maxMessages === Infinity ? 'all' : maxMessages,
|
|
7393
|
+
outputDir: outputDirMode
|
|
7269
7394
|
}
|
|
7270
7395
|
});
|
|
7271
7396
|
|
|
@@ -7301,7 +7426,12 @@ async function convertSessionToDerived(params = {}) {
|
|
|
7301
7426
|
target,
|
|
7302
7427
|
truncated: !!extracted.truncated,
|
|
7303
7428
|
maxMessages: maxMessagesLabel,
|
|
7304
|
-
session: summary ? {
|
|
7429
|
+
session: attachSessionNativeStatus(summary ? {
|
|
7430
|
+
...summary,
|
|
7431
|
+
derived: true,
|
|
7432
|
+
convertedFrom: source,
|
|
7433
|
+
convertedFromLabel: source === 'codex' ? 'Codex' : 'Claude Code'
|
|
7434
|
+
} : {
|
|
7305
7435
|
source: target,
|
|
7306
7436
|
sourceLabel: target === 'codex' ? 'Codex' : 'Claude Code',
|
|
7307
7437
|
sessionId: derivedSessionId,
|
|
@@ -7319,9 +7449,158 @@ async function convertSessionToDerived(params = {}) {
|
|
|
7319
7449
|
__messageCountExact: true,
|
|
7320
7450
|
filePath: outputPath,
|
|
7321
7451
|
derived: true,
|
|
7452
|
+
convertedFrom: source,
|
|
7453
|
+
convertedFromLabel: source === 'codex' ? 'Codex' : 'Claude Code',
|
|
7322
7454
|
keywords: [],
|
|
7323
7455
|
capabilities: {}
|
|
7456
|
+
})
|
|
7457
|
+
};
|
|
7458
|
+
}
|
|
7459
|
+
|
|
7460
|
+
|
|
7461
|
+
async function importDerivedSessionToNative(params = {}) {
|
|
7462
|
+
const source = normalizeSessionDerivedTarget(params.source);
|
|
7463
|
+
if (!source) {
|
|
7464
|
+
return { error: 'Invalid source', errorCode: 'INVALID_SOURCE' };
|
|
7465
|
+
}
|
|
7466
|
+
const filePath = resolveSessionFilePath(source, getSessionFileArg(params), params.sessionId);
|
|
7467
|
+
if (!filePath) {
|
|
7468
|
+
return { error: 'Session file not found', errorCode: 'SESSION_FILE_NOT_FOUND' };
|
|
7469
|
+
}
|
|
7470
|
+
|
|
7471
|
+
const summary = (source === 'claude' ? parseClaudeSessionSummary(filePath) : parseCodexSessionSummary(filePath))
|
|
7472
|
+
|| buildSessionSummaryFallback(source, filePath, params.sessionId);
|
|
7473
|
+
const sessionId = summary.sessionId || params.sessionId || path.basename(filePath, '.jsonl');
|
|
7474
|
+
const nativePath = resolveNativeSessionFilePath(source, sessionId, summary.cwd || '');
|
|
7475
|
+
if (!nativePath) {
|
|
7476
|
+
return { error: 'Native session path unavailable', errorCode: 'NATIVE_SESSION_PATH_UNAVAILABLE' };
|
|
7477
|
+
}
|
|
7478
|
+
|
|
7479
|
+
const resolvedSourcePath = path.resolve(expandHomePath(filePath));
|
|
7480
|
+
const resolvedNativePath = path.resolve(expandHomePath(nativePath));
|
|
7481
|
+
const overwrite = params.overwrite === true || params.confirmOverwrite === true || params.force === true;
|
|
7482
|
+
const hadNativeBefore = fs.existsSync(resolvedNativePath);
|
|
7483
|
+
if (resolvedSourcePath === resolvedNativePath) {
|
|
7484
|
+
return {
|
|
7485
|
+
success: true,
|
|
7486
|
+
imported: false,
|
|
7487
|
+
alreadyNative: true,
|
|
7488
|
+
source,
|
|
7489
|
+
sessionId,
|
|
7490
|
+
filePath: resolvedNativePath,
|
|
7491
|
+
nativePath: resolvedNativePath,
|
|
7492
|
+
nativeAvailable: true,
|
|
7493
|
+
session: attachSessionNativeStatus({ ...summary, filePath: resolvedNativePath, derived: true })
|
|
7494
|
+
};
|
|
7495
|
+
}
|
|
7496
|
+
if (fs.existsSync(resolvedNativePath) && !overwrite) {
|
|
7497
|
+
return {
|
|
7498
|
+
error: 'Native session already exists',
|
|
7499
|
+
errorCode: 'NATIVE_SESSION_EXISTS',
|
|
7500
|
+
conflict: true,
|
|
7501
|
+
source,
|
|
7502
|
+
sessionId,
|
|
7503
|
+
filePath: resolvedSourcePath,
|
|
7504
|
+
nativePath: resolvedNativePath,
|
|
7505
|
+
nativeAvailable: true,
|
|
7506
|
+
session: attachSessionNativeStatus({ ...summary, filePath: resolvedSourcePath, derived: true })
|
|
7507
|
+
};
|
|
7508
|
+
}
|
|
7509
|
+
|
|
7510
|
+
const targetMetaPath = getDerivedSessionMetaPath(resolvedNativePath);
|
|
7511
|
+
const indexPath = source === 'claude' ? path.join(path.dirname(resolvedNativePath), 'sessions-index.json') : '';
|
|
7512
|
+
const tmpNativePath = `${resolvedNativePath}.tmp-${process.pid}-${Date.now()}`;
|
|
7513
|
+
let previousNative = null;
|
|
7514
|
+
let previousMeta = null;
|
|
7515
|
+
let previousIndex = null;
|
|
7516
|
+
try {
|
|
7517
|
+
previousNative = hadNativeBefore ? fs.readFileSync(resolvedNativePath) : null;
|
|
7518
|
+
previousMeta = targetMetaPath && fs.existsSync(targetMetaPath)
|
|
7519
|
+
? fs.readFileSync(targetMetaPath)
|
|
7520
|
+
: null;
|
|
7521
|
+
previousIndex = indexPath && fs.existsSync(indexPath)
|
|
7522
|
+
? fs.readFileSync(indexPath)
|
|
7523
|
+
: null;
|
|
7524
|
+
ensureDir(path.dirname(resolvedNativePath));
|
|
7525
|
+
fs.copyFileSync(resolvedSourcePath, tmpNativePath);
|
|
7526
|
+
const sourceMetaPath = getDerivedSessionMetaPath(resolvedSourcePath);
|
|
7527
|
+
let meta = readDerivedSessionMeta(resolvedSourcePath) || {};
|
|
7528
|
+
if (!meta || typeof meta !== 'object' || Array.isArray(meta)) meta = {};
|
|
7529
|
+
meta.version = meta.version || 1;
|
|
7530
|
+
meta.importedAt = new Date().toISOString();
|
|
7531
|
+
meta.target = {
|
|
7532
|
+
...(meta.target && typeof meta.target === 'object' ? meta.target : {}),
|
|
7533
|
+
type: source,
|
|
7534
|
+
sessionId,
|
|
7535
|
+
filePath: resolvedNativePath
|
|
7536
|
+
};
|
|
7537
|
+
if (sourceMetaPath && fs.existsSync(sourceMetaPath)) {
|
|
7538
|
+
writeJsonAtomic(targetMetaPath, meta);
|
|
7539
|
+
} else {
|
|
7540
|
+
writeJsonAtomic(targetMetaPath, meta);
|
|
7324
7541
|
}
|
|
7542
|
+
if (source === 'claude') {
|
|
7543
|
+
ensureClaudeSessionsIndex(indexPath, summary.cwd || '');
|
|
7544
|
+
upsertClaudeSessionIndexEntry(indexPath, resolvedNativePath, {
|
|
7545
|
+
...summary,
|
|
7546
|
+
source: 'claude',
|
|
7547
|
+
trashId: sessionId,
|
|
7548
|
+
trashFileName: `${sessionId}.jsonl`,
|
|
7549
|
+
sessionId,
|
|
7550
|
+
claudeIndexEntry: { projectPath: summary.cwd || '' }
|
|
7551
|
+
});
|
|
7552
|
+
}
|
|
7553
|
+
if (hadNativeBefore) {
|
|
7554
|
+
fs.unlinkSync(resolvedNativePath);
|
|
7555
|
+
}
|
|
7556
|
+
fs.renameSync(tmpNativePath, resolvedNativePath);
|
|
7557
|
+
} catch (e) {
|
|
7558
|
+
try {
|
|
7559
|
+
if (fs.existsSync(tmpNativePath)) fs.unlinkSync(tmpNativePath);
|
|
7560
|
+
} catch (_) {}
|
|
7561
|
+
try {
|
|
7562
|
+
if (previousNative) {
|
|
7563
|
+
ensureDir(path.dirname(resolvedNativePath));
|
|
7564
|
+
fs.writeFileSync(resolvedNativePath, previousNative);
|
|
7565
|
+
} else if (!hadNativeBefore && fs.existsSync(resolvedNativePath)) {
|
|
7566
|
+
fs.unlinkSync(resolvedNativePath);
|
|
7567
|
+
}
|
|
7568
|
+
} catch (_) {}
|
|
7569
|
+
try {
|
|
7570
|
+
if (previousMeta) {
|
|
7571
|
+
ensureDir(path.dirname(targetMetaPath));
|
|
7572
|
+
fs.writeFileSync(targetMetaPath, previousMeta);
|
|
7573
|
+
} else if (targetMetaPath && fs.existsSync(targetMetaPath)) {
|
|
7574
|
+
fs.unlinkSync(targetMetaPath);
|
|
7575
|
+
}
|
|
7576
|
+
} catch (_) {}
|
|
7577
|
+
try {
|
|
7578
|
+
if (indexPath) {
|
|
7579
|
+
if (previousIndex) {
|
|
7580
|
+
ensureDir(path.dirname(indexPath));
|
|
7581
|
+
fs.writeFileSync(indexPath, previousIndex);
|
|
7582
|
+
} else if (fs.existsSync(indexPath)) {
|
|
7583
|
+
fs.unlinkSync(indexPath);
|
|
7584
|
+
}
|
|
7585
|
+
}
|
|
7586
|
+
} catch (_) {}
|
|
7587
|
+
return { error: `Import to native failed: ${e.message}`, errorCode: 'IMPORT_DERIVED_SESSION_FAILED', reason: e.message };
|
|
7588
|
+
}
|
|
7589
|
+
|
|
7590
|
+
invalidateSessionListCache();
|
|
7591
|
+
const importedSummary = (source === 'claude' ? parseClaudeSessionSummary(resolvedNativePath) : parseCodexSessionSummary(resolvedNativePath))
|
|
7592
|
+
|| { ...summary, filePath: resolvedNativePath };
|
|
7593
|
+
return {
|
|
7594
|
+
success: true,
|
|
7595
|
+
imported: true,
|
|
7596
|
+
source,
|
|
7597
|
+
sessionId,
|
|
7598
|
+
filePath: resolvedNativePath,
|
|
7599
|
+
nativePath: resolvedNativePath,
|
|
7600
|
+
nativeAvailable: true,
|
|
7601
|
+
previousFilePath: resolvedSourcePath,
|
|
7602
|
+
overwritten: hadNativeBefore && overwrite,
|
|
7603
|
+
session: attachSessionNativeStatus({ ...importedSummary, filePath: resolvedNativePath, derived: true })
|
|
7325
7604
|
};
|
|
7326
7605
|
}
|
|
7327
7606
|
|
|
@@ -8800,13 +9079,22 @@ async function restoreCodexDir(payload) {
|
|
|
8800
9079
|
}
|
|
8801
9080
|
|
|
8802
9081
|
// CLI: 一行写入 Claude Code 配置
|
|
8803
|
-
function cmdClaude(
|
|
9082
|
+
async function cmdClaude(args = []) {
|
|
9083
|
+
const argv = Array.isArray(args) ? args : [];
|
|
9084
|
+
// 无参数 → 代理启动
|
|
9085
|
+
if (argv.length === 0 || (argv.length === 1 && argv[0] === undefined)) {
|
|
9086
|
+
return runProxyCommand('Claude', 'claude', [], '', { autoFlag: '--dangerously-skip-permissions' });
|
|
9087
|
+
}
|
|
9088
|
+
// 有参数 → 配置写入
|
|
9089
|
+
const [baseUrl, apiKey, model] = argv;
|
|
8804
9090
|
const normalizedBaseUrl = typeof baseUrl === 'string' ? baseUrl.trim() : '';
|
|
8805
9091
|
const normalizedKey = typeof apiKey === 'string' ? apiKey.trim() : '';
|
|
8806
9092
|
const normalizedModel = typeof model === 'string' && model.trim()
|
|
8807
9093
|
? model.trim()
|
|
8808
9094
|
: DEFAULT_CLAUDE_MODEL;
|
|
8809
9095
|
|
|
9096
|
+
const silent = false;
|
|
9097
|
+
|
|
8810
9098
|
if (!normalizedBaseUrl || !normalizedKey) {
|
|
8811
9099
|
if (!silent) {
|
|
8812
9100
|
console.error('用法: codexmate claude <BaseURL> <API密钥> [模型]');
|
|
@@ -8841,7 +9129,7 @@ function cmdClaude(baseUrl, apiKey, model, silent = false) {
|
|
|
8841
9129
|
console.log();
|
|
8842
9130
|
}
|
|
8843
9131
|
|
|
8844
|
-
return
|
|
9132
|
+
return 0;
|
|
8845
9133
|
}
|
|
8846
9134
|
|
|
8847
9135
|
function commandExists(command, args = '') {
|
|
@@ -10547,6 +10835,9 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
10547
10835
|
case 'convert-session':
|
|
10548
10836
|
result = await convertSessionToDerived(params || {});
|
|
10549
10837
|
break;
|
|
10838
|
+
case 'import-derived-session':
|
|
10839
|
+
result = await importDerivedSessionToNative(params || {});
|
|
10840
|
+
break;
|
|
10550
10841
|
case 'delete-session':
|
|
10551
10842
|
result = await deleteSessionData(params || {});
|
|
10552
10843
|
break;
|
|
@@ -12401,8 +12692,9 @@ async function runProxyCommandWithQueuedFollowUps(selectedBin, finalArgs = [], q
|
|
|
12401
12692
|
|
|
12402
12693
|
async function runProxyCommand(displayName, binNames, args = [], installTip = '', runtimeOptions = {}) {
|
|
12403
12694
|
const extraArgs = Array.isArray(args) ? args.filter(arg => arg !== undefined) : [];
|
|
12404
|
-
const
|
|
12405
|
-
const
|
|
12695
|
+
const autoFlag = typeof runtimeOptions.autoFlag === 'string' && runtimeOptions.autoFlag ? runtimeOptions.autoFlag : '--yolo';
|
|
12696
|
+
const hasAutoFlag = extraArgs.includes(autoFlag);
|
|
12697
|
+
const finalArgs = hasAutoFlag ? extraArgs : [autoFlag, ...extraArgs];
|
|
12406
12698
|
|
|
12407
12699
|
const names = Array.isArray(binNames) ? binNames : [binNames];
|
|
12408
12700
|
let selectedBin = names[0];
|
|
@@ -15345,7 +15637,8 @@ function printMainHelp() {
|
|
|
15345
15637
|
console.log(' codexmate use <模型> 切换模型');
|
|
15346
15638
|
console.log(' codexmate add <名称> <URL> [密钥] [--bridge <openai>]');
|
|
15347
15639
|
console.log(' codexmate delete <名称> 删除提供商');
|
|
15348
|
-
console.log(' codexmate claude
|
|
15640
|
+
console.log(' codexmate claude 等同于 claude --dangerously-skip-permissions');
|
|
15641
|
+
console.log(' codexmate claude <BaseURL> <API密钥> [模型] 写入 Claude Code 配置');
|
|
15349
15642
|
console.log(' codexmate auth <list|import|switch|delete|status> 认证管理');
|
|
15350
15643
|
console.log(' codexmate add-model <模型> 添加模型');
|
|
15351
15644
|
console.log(' codexmate delete-model <模型> 删除模型');
|
|
@@ -15432,7 +15725,11 @@ async function main() {
|
|
|
15432
15725
|
break;
|
|
15433
15726
|
}
|
|
15434
15727
|
case 'delete': cmdDelete(args[1]); break;
|
|
15435
|
-
case 'claude':
|
|
15728
|
+
case 'claude': {
|
|
15729
|
+
const exitCode = await cmdClaude(args.slice(1));
|
|
15730
|
+
process.exit(exitCode);
|
|
15731
|
+
break;
|
|
15732
|
+
}
|
|
15436
15733
|
case 'add-model': cmdAddModel(args[1]); break;
|
|
15437
15734
|
case 'delete-model': cmdDeleteModel(args[1]); break;
|
|
15438
15735
|
case 'auth': cmdAuth(args.slice(1)); break;
|
package/package.json
CHANGED
package/web-ui/app.js
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { runLatestOnlyQueue } from '../logic.mjs';
|
|
2
2
|
import { normalizeConfigTemplateDiffConfirmEnabled } from './config-template-confirm-pref.mjs';
|
|
3
|
-
import {
|
|
4
|
-
getConvertTargetSource,
|
|
5
|
-
normalizeSessionConvertSource
|
|
6
|
-
} from '../logic.session-convert.mjs';
|
|
7
|
-
import { syncSessionsFilterUrl } from './sessions-filters-url.mjs';
|
|
8
3
|
|
|
9
4
|
function hasResponseError(response) {
|
|
10
5
|
if (!response || typeof response !== 'object') {
|
|
@@ -80,77 +75,6 @@ export function createCodexConfigMethods(options = {}) {
|
|
|
80
75
|
}
|
|
81
76
|
},
|
|
82
77
|
|
|
83
|
-
async convertSession(session) {
|
|
84
|
-
const source = normalizeSessionConvertSource(session && session.source ? session.source : '');
|
|
85
|
-
const target = getConvertTargetSource(source);
|
|
86
|
-
if (!source || !target) {
|
|
87
|
-
this.showMessage('不支持此操作', 'error');
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
const key = this.getSessionExportKey(session);
|
|
91
|
-
if (this.sessionConverting[key]) return;
|
|
92
|
-
this.sessionConverting[key] = true;
|
|
93
|
-
try {
|
|
94
|
-
const res = await api('convert-session', {
|
|
95
|
-
source,
|
|
96
|
-
target,
|
|
97
|
-
sessionId: session.sessionId,
|
|
98
|
-
filePath: session.filePath,
|
|
99
|
-
maxMessages: 'all'
|
|
100
|
-
});
|
|
101
|
-
if (res && res.error) {
|
|
102
|
-
this.showMessage(res.error, 'error');
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
const converted = res && res.session ? res.session : null;
|
|
106
|
-
if (!converted) {
|
|
107
|
-
this.showMessage('转换失败', 'error');
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
if (res && res.truncated) {
|
|
111
|
-
const maxLabel = res.maxMessages === 'all' ? 'all' : res.maxMessages;
|
|
112
|
-
const targetLabel = converted && converted.sourceLabel ? String(converted.sourceLabel).trim() : '';
|
|
113
|
-
if (targetLabel && typeof this.sessionFilterSource === 'string' && this.sessionFilterSource !== converted.source) {
|
|
114
|
-
this.showMessage(`已生成派生会话(来源:${targetLabel},已截断:最多 ${maxLabel} 条消息)`, 'info');
|
|
115
|
-
} else {
|
|
116
|
-
this.showMessage(`已生成派生会话(已截断:最多 ${maxLabel} 条消息)`, 'info');
|
|
117
|
-
}
|
|
118
|
-
} else {
|
|
119
|
-
const targetLabel = converted && converted.sourceLabel ? String(converted.sourceLabel).trim() : '';
|
|
120
|
-
if (targetLabel && typeof this.sessionFilterSource === 'string' && this.sessionFilterSource !== converted.source) {
|
|
121
|
-
this.showMessage(`已生成派生会话(来源:${targetLabel})`, 'success');
|
|
122
|
-
} else {
|
|
123
|
-
this.showMessage('已生成派生会话', 'success');
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (converted && converted.source && typeof this.sessionFilterSource === 'string') {
|
|
128
|
-
if (this.sessionFilterSource !== converted.source) {
|
|
129
|
-
this.sessionFilterSource = converted.source;
|
|
130
|
-
if (typeof this.refreshSessionPathOptions === 'function') {
|
|
131
|
-
this.refreshSessionPathOptions(this.sessionFilterSource);
|
|
132
|
-
}
|
|
133
|
-
if (typeof this.persistSessionFilterCache === 'function') {
|
|
134
|
-
this.persistSessionFilterCache();
|
|
135
|
-
}
|
|
136
|
-
syncSessionsFilterUrl(this);
|
|
137
|
-
this.sessionsList = [converted];
|
|
138
|
-
} else {
|
|
139
|
-
const list = Array.isArray(this.sessionsList) ? this.sessionsList : [];
|
|
140
|
-
const next = [converted, ...list.filter(item => !(item && item.filePath === converted.filePath && item.source === converted.source))];
|
|
141
|
-
this.sessionsList = next;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
if (typeof this.selectSession === 'function') {
|
|
145
|
-
await this.selectSession(converted);
|
|
146
|
-
}
|
|
147
|
-
} catch (e) {
|
|
148
|
-
this.showMessage('转换失败', 'error');
|
|
149
|
-
} finally {
|
|
150
|
-
this.sessionConverting[key] = false;
|
|
151
|
-
}
|
|
152
|
-
},
|
|
153
|
-
|
|
154
78
|
async quickSwitchProvider(name) {
|
|
155
79
|
const target = String(name || '').trim();
|
|
156
80
|
const visualTarget = String(this.providerSwitchDisplayTarget || '').trim();
|