@siteboon/claude-code-ui 1.21.0 → 1.23.0

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.
@@ -66,6 +66,7 @@ import sqlite3 from 'sqlite3';
66
66
  import { open } from 'sqlite';
67
67
  import os from 'os';
68
68
  import sessionManager from './sessionManager.js';
69
+ import { applyCustomSessionNames } from './database/db.js';
69
70
 
70
71
  // Import TaskMaster detection functions
71
72
  async function detectTaskMasterFolder(projectPath) {
@@ -458,6 +459,7 @@ async function getProjects(progressCallback = null) {
458
459
  total: 0
459
460
  };
460
461
  }
462
+ applyCustomSessionNames(project.sessions, 'claude');
461
463
 
462
464
  // Also fetch Cursor sessions for this project
463
465
  try {
@@ -466,6 +468,7 @@ async function getProjects(progressCallback = null) {
466
468
  console.warn(`Could not load Cursor sessions for project ${entry.name}:`, e.message);
467
469
  project.cursorSessions = [];
468
470
  }
471
+ applyCustomSessionNames(project.cursorSessions, 'cursor');
469
472
 
470
473
  // Also fetch Codex sessions for this project
471
474
  try {
@@ -476,6 +479,7 @@ async function getProjects(progressCallback = null) {
476
479
  console.warn(`Could not load Codex sessions for project ${entry.name}:`, e.message);
477
480
  project.codexSessions = [];
478
481
  }
482
+ applyCustomSessionNames(project.codexSessions, 'codex');
479
483
 
480
484
  // Also fetch Gemini sessions for this project
481
485
  try {
@@ -484,6 +488,7 @@ async function getProjects(progressCallback = null) {
484
488
  console.warn(`Could not load Gemini sessions for project ${entry.name}:`, e.message);
485
489
  project.geminiSessions = [];
486
490
  }
491
+ applyCustomSessionNames(project.geminiSessions, 'gemini');
487
492
 
488
493
  // Add TaskMaster detection
489
494
  try {
@@ -567,6 +572,7 @@ async function getProjects(progressCallback = null) {
567
572
  } catch (e) {
568
573
  console.warn(`Could not load Cursor sessions for manual project ${projectName}:`, e.message);
569
574
  }
575
+ applyCustomSessionNames(project.cursorSessions, 'cursor');
570
576
 
571
577
  // Try to fetch Codex sessions for manual projects too
572
578
  try {
@@ -576,6 +582,7 @@ async function getProjects(progressCallback = null) {
576
582
  } catch (e) {
577
583
  console.warn(`Could not load Codex sessions for manual project ${projectName}:`, e.message);
578
584
  }
585
+ applyCustomSessionNames(project.codexSessions, 'codex');
579
586
 
580
587
  // Try to fetch Gemini sessions for manual projects too
581
588
  try {
@@ -583,6 +590,7 @@ async function getProjects(progressCallback = null) {
583
590
  } catch (e) {
584
591
  console.warn(`Could not load Gemini sessions for manual project ${projectName}:`, e.message);
585
592
  }
593
+ applyCustomSessionNames(project.geminiSessions, 'gemini');
586
594
 
587
595
  // Add TaskMaster detection for manual projects
588
596
  try {
@@ -1071,10 +1079,13 @@ async function renameProject(projectName, newDisplayName) {
1071
1079
 
1072
1080
  if (!newDisplayName || newDisplayName.trim() === '') {
1073
1081
  // Remove custom name if empty, will fall back to auto-generated
1074
- delete config[projectName];
1082
+ if (config[projectName]) {
1083
+ delete config[projectName].displayName;
1084
+ }
1075
1085
  } else {
1076
- // Set custom display name
1086
+ // Set custom display name, preserving other properties (manuallyAdded, originalPath)
1077
1087
  config[projectName] = {
1088
+ ...config[projectName],
1078
1089
  displayName: newDisplayName.trim()
1079
1090
  };
1080
1091
  }
@@ -1479,6 +1490,23 @@ async function getCodexSessions(projectPath, options = {}) {
1479
1490
  }
1480
1491
  }
1481
1492
 
1493
+ function isVisibleCodexUserMessage(payload) {
1494
+ if (!payload || payload.type !== 'user_message') {
1495
+ return false;
1496
+ }
1497
+
1498
+ // Codex logs internal context (environment, instructions) as non-plain user_message kinds.
1499
+ if (payload.kind && payload.kind !== 'plain') {
1500
+ return false;
1501
+ }
1502
+
1503
+ if (typeof payload.message !== 'string' || payload.message.trim().length === 0) {
1504
+ return false;
1505
+ }
1506
+
1507
+ return true;
1508
+ }
1509
+
1482
1510
  // Parse a Codex session JSONL file to extract metadata
1483
1511
  async function parseCodexSessionFile(filePath) {
1484
1512
  try {
@@ -1514,8 +1542,8 @@ async function parseCodexSessionFile(filePath) {
1514
1542
  };
1515
1543
  }
1516
1544
 
1517
- // Count messages and extract user messages for summary
1518
- if (entry.type === 'event_msg' && entry.payload?.type === 'user_message') {
1545
+ // Count visible user messages and extract summary from the latest plain user input.
1546
+ if (entry.type === 'event_msg' && isVisibleCodexUserMessage(entry.payload)) {
1519
1547
  messageCount++;
1520
1548
  if (entry.payload.message) {
1521
1549
  lastUserMessage = entry.payload.message;
@@ -1622,25 +1650,36 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) {
1622
1650
  };
1623
1651
  }
1624
1652
  }
1653
+
1654
+ // Use event_msg.user_message for user-visible inputs.
1655
+ if (entry.type === 'event_msg' && isVisibleCodexUserMessage(entry.payload)) {
1656
+ messages.push({
1657
+ type: 'user',
1658
+ timestamp: entry.timestamp,
1659
+ message: {
1660
+ role: 'user',
1661
+ content: entry.payload.message
1662
+ }
1663
+ });
1664
+ }
1625
1665
 
1626
- // Extract messages from response_item
1627
- if (entry.type === 'response_item' && entry.payload?.type === 'message') {
1666
+ // response_item.message may include internal prompts for non-assistant roles.
1667
+ // Keep only assistant output from response_item.
1668
+ if (
1669
+ entry.type === 'response_item' &&
1670
+ entry.payload?.type === 'message' &&
1671
+ entry.payload.role === 'assistant'
1672
+ ) {
1628
1673
  const content = entry.payload.content;
1629
- const role = entry.payload.role || 'assistant';
1630
1674
  const textContent = extractText(content);
1631
1675
 
1632
- // Skip system context messages (environment_context)
1633
- if (textContent?.includes('<environment_context>')) {
1634
- continue;
1635
- }
1636
-
1637
1676
  // Only add if there's actual content
1638
1677
  if (textContent?.trim()) {
1639
1678
  messages.push({
1640
- type: role === 'user' ? 'user' : 'assistant',
1679
+ type: 'assistant',
1641
1680
  timestamp: entry.timestamp,
1642
1681
  message: {
1643
- role: role,
1682
+ role: 'assistant',
1644
1683
  content: textContent
1645
1684
  }
1646
1685
  });
@@ -14,13 +14,14 @@ router.get('/claude/status', async (req, res) => {
14
14
  return res.json({
15
15
  authenticated: true,
16
16
  email: credentialsResult.email || 'Authenticated',
17
- method: 'credentials_file'
17
+ method: credentialsResult.method // 'api_key' or 'credentials_file'
18
18
  });
19
19
  }
20
20
 
21
21
  return res.json({
22
22
  authenticated: false,
23
23
  email: null,
24
+ method: null,
24
25
  error: credentialsResult.error || 'Not authenticated'
25
26
  });
26
27
 
@@ -29,6 +30,7 @@ router.get('/claude/status', async (req, res) => {
29
30
  res.status(500).json({
30
31
  authenticated: false,
31
32
  email: null,
33
+ method: null,
32
34
  error: error.message
33
35
  });
34
36
  }
@@ -115,6 +117,20 @@ router.get('/gemini/status', async (req, res) => {
115
117
  * - method: 'api_key' for env var, 'credentials_file' for OAuth tokens
116
118
  */
117
119
  async function checkClaudeCredentials() {
120
+ // Priority 1: Check for ANTHROPIC_API_KEY environment variable
121
+ // The SDK checks this first and uses it if present, even if OAuth tokens exist.
122
+ // When set, API calls are charged via pay-as-you-go rates instead of subscription.
123
+ if (process.env.ANTHROPIC_API_KEY && process.env.ANTHROPIC_API_KEY.trim()) {
124
+ return {
125
+ authenticated: true,
126
+ email: 'API Key Auth',
127
+ method: 'api_key'
128
+ };
129
+ }
130
+
131
+ // Priority 2: Check ~/.claude/.credentials.json for OAuth tokens
132
+ // This is the standard authentication method used by Claude CLI after running
133
+ // 'claude /login' or 'claude setup-token' commands.
118
134
  try {
119
135
  const credPath = path.join(os.homedir(), '.claude', '.credentials.json');
120
136
  const content = await fs.readFile(credPath, 'utf8');
@@ -127,19 +143,22 @@ async function checkClaudeCredentials() {
127
143
  if (!isExpired) {
128
144
  return {
129
145
  authenticated: true,
130
- email: creds.email || creds.user || null
146
+ email: creds.email || creds.user || null,
147
+ method: 'credentials_file'
131
148
  };
132
149
  }
133
150
  }
134
151
 
135
152
  return {
136
153
  authenticated: false,
137
- email: null
154
+ email: null,
155
+ method: null
138
156
  };
139
157
  } catch (error) {
140
158
  return {
141
159
  authenticated: false,
142
- email: null
160
+ email: null,
161
+ method: null
143
162
  };
144
163
  }
145
164
  }
@@ -5,6 +5,7 @@ import path from 'path';
5
5
  import os from 'os';
6
6
  import TOML from '@iarna/toml';
7
7
  import { getCodexSessions, getCodexSessionMessages, deleteCodexSession } from '../projects.js';
8
+ import { applyCustomSessionNames, sessionNamesDb } from '../database/db.js';
8
9
 
9
10
  const router = express.Router();
10
11
 
@@ -59,6 +60,7 @@ router.get('/sessions', async (req, res) => {
59
60
  }
60
61
 
61
62
  const sessions = await getCodexSessions(projectPath);
63
+ applyCustomSessionNames(sessions, 'codex');
62
64
  res.json({ success: true, sessions });
63
65
  } catch (error) {
64
66
  console.error('Error fetching Codex sessions:', error);
@@ -88,6 +90,7 @@ router.delete('/sessions/:sessionId', async (req, res) => {
88
90
  try {
89
91
  const { sessionId } = req.params;
90
92
  await deleteCodexSession(sessionId);
93
+ sessionNamesDb.deleteName(sessionId, 'codex');
91
94
  res.json({ success: true });
92
95
  } catch (error) {
93
96
  console.error(`Error deleting Codex session ${req.params.sessionId}:`, error);
@@ -7,6 +7,7 @@ import sqlite3 from 'sqlite3';
7
7
  import { open } from 'sqlite';
8
8
  import crypto from 'crypto';
9
9
  import { CURSOR_MODELS } from '../../shared/modelConstants.js';
10
+ import { applyCustomSessionNames } from '../database/db.js';
10
11
 
11
12
  const router = express.Router();
12
13
 
@@ -560,8 +561,10 @@ router.get('/sessions', async (req, res) => {
560
561
  return new Date(b.createdAt) - new Date(a.createdAt);
561
562
  });
562
563
 
563
- res.json({
564
- success: true,
564
+ applyCustomSessionNames(sessions, 'cursor');
565
+
566
+ res.json({
567
+ success: true,
565
568
  sessions: sessions,
566
569
  cwdId: cwdId,
567
570
  path: cursorChatsPath
@@ -1,5 +1,6 @@
1
1
  import express from 'express';
2
2
  import sessionManager from '../sessionManager.js';
3
+ import { sessionNamesDb } from '../database/db.js';
3
4
 
4
5
  const router = express.Router();
5
6
 
@@ -36,6 +37,7 @@ router.delete('/sessions/:sessionId', async (req, res) => {
36
37
  }
37
38
 
38
39
  await sessionManager.deleteSession(sessionId);
40
+ sessionNamesDb.deleteName(sessionId, 'gemini');
39
41
  res.json({ success: true });
40
42
  } catch (error) {
41
43
  console.error(`Error deleting Gemini session ${req.params.sessionId}:`, error);
@@ -28,6 +28,8 @@ export const CLAUDE_MODELS = {
28
28
  */
29
29
  export const CURSOR_MODELS = {
30
30
  OPTIONS: [
31
+ { value: 'opus-4.6-thinking', label: 'Claude 4.6 Opus (Thinking)' },
32
+ { value: 'gpt-5.3-codex', label: 'GPT-5.3' },
31
33
  { value: 'gpt-5.2-high', label: 'GPT-5.2 High' },
32
34
  { value: 'gemini-3-pro', label: 'Gemini 3 Pro' },
33
35
  { value: 'opus-4.5-thinking', label: 'Claude 4.5 Opus (Thinking)' },
@@ -47,7 +49,7 @@ export const CURSOR_MODELS = {
47
49
  { value: 'grok', label: 'Grok' }
48
50
  ],
49
51
 
50
- DEFAULT: 'gpt-5'
52
+ DEFAULT: 'gpt-5-3-codex'
51
53
  };
52
54
 
53
55
  /**
@@ -55,6 +57,8 @@ export const CURSOR_MODELS = {
55
57
  */
56
58
  export const CODEX_MODELS = {
57
59
  OPTIONS: [
60
+ { value: 'gpt-5.4', label: 'GPT-5.4' },
61
+ { value: 'gpt-5.3-codex', label: 'GPT-5.3 Codex' },
58
62
  { value: 'gpt-5.3-codex', label: 'GPT-5.3 Codex' },
59
63
  { value: 'gpt-5.2-codex', label: 'GPT-5.2 Codex' },
60
64
  { value: 'gpt-5.2', label: 'GPT-5.2' },
@@ -63,7 +67,7 @@ export const CODEX_MODELS = {
63
67
  { value: 'o4-mini', label: 'O4-mini' }
64
68
  ],
65
69
 
66
- DEFAULT: 'gpt-5.3-codex'
70
+ DEFAULT: 'gpt-5.4'
67
71
  };
68
72
 
69
73
  /**