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/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, -5)
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
- return /-\d{8}-\d{6}-[0-9a-f]{6}$/i.test(base);
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, -5)}.meta.json`
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
- return /-\d{8}-\d{6}-[0-9a-f]{6}$/i.test(base);
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 outputDir = target === 'codex'
7201
- ? getCodexSessionsDir()
7202
- : (target === 'claude'
7203
- ? (resolveClaudeProjectDirForCwd(extracted.cwd || '') || path.join(getClaudeProjectsDir(), 'codexmate-derived'))
7204
- : buildDerivedSessionOutputDir(target, source, sourceKey));
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
- const cwd = typeof extracted.cwd === 'string' ? extracted.cwd : '';
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 ? { ...summary, derived: true } : {
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(baseUrl, apiKey, model, silent = false) {
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 result;
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 hasYolo = extraArgs.includes('--yolo');
12405
- const finalArgs = hasYolo ? extraArgs : ['--yolo', ...extraArgs];
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 <BaseURL> <API密钥> [模型] 写入 Claude Code 配置');
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': cmdClaude(args[1], args[2], args[3]); break;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codexmate",
3
- "version": "0.0.30",
3
+ "version": "0.0.31",
4
4
  "description": "Codex/Claude Code/OpenClaw 配置、会话与任务编排 CLI + Web 工具",
5
5
  "main": "cli.js",
6
6
  "bin": {
package/web-ui/app.js CHANGED
@@ -195,6 +195,7 @@ document.addEventListener('DOMContentLoaded', () => {
195
195
  },
196
196
  sessionExporting: {},
197
197
  sessionConverting: {},
198
+ sessionImportingNative: {},
198
199
  sessionCloning: {},
199
200
  sessionDeleting: {},
200
201
  activeSession: null,
@@ -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();