@epiphytic/claudecodeui 1.0.1 → 1.1.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.
@@ -0,0 +1,196 @@
1
+ /**
2
+ * PROJECTS CACHE MODULE
3
+ * =====================
4
+ *
5
+ * In-memory cache for projects data with ETag support.
6
+ * Updated by the chokidar watcher when project files change.
7
+ * Mirrors the sessions-cache.js pattern for consistency.
8
+ */
9
+
10
+ import crypto from "crypto";
11
+
12
+ // Cache state
13
+ let cachedProjects = [];
14
+ let cacheVersion = 0;
15
+ let cacheTimestamp = null;
16
+
17
+ /**
18
+ * Timeframe definitions in milliseconds
19
+ * (Same as sessions-cache.js for consistency)
20
+ */
21
+ const TIMEFRAME_MS = {
22
+ "1h": 60 * 60 * 1000,
23
+ "8h": 8 * 60 * 60 * 1000,
24
+ "1d": 24 * 60 * 60 * 1000,
25
+ "1w": 7 * 24 * 60 * 60 * 1000,
26
+ "2w": 14 * 24 * 60 * 60 * 1000,
27
+ "1m": 30 * 24 * 60 * 60 * 1000,
28
+ all: Infinity,
29
+ };
30
+
31
+ /**
32
+ * Calculate last activity timestamp for a project
33
+ * Based on the most recent session across all providers
34
+ */
35
+ function calculateLastActivity(project) {
36
+ let lastActivity = null;
37
+
38
+ // Check Claude sessions
39
+ if (project.sessions && project.sessions.length > 0) {
40
+ for (const session of project.sessions) {
41
+ const sessionDate = new Date(session.lastActivity);
42
+ if (!lastActivity || sessionDate > lastActivity) {
43
+ lastActivity = sessionDate;
44
+ }
45
+ }
46
+ }
47
+
48
+ // Check Cursor sessions
49
+ if (project.cursorSessions && project.cursorSessions.length > 0) {
50
+ for (const session of project.cursorSessions) {
51
+ const sessionDate = new Date(session.createdAt || session.lastActivity);
52
+ if (!lastActivity || sessionDate > lastActivity) {
53
+ lastActivity = sessionDate;
54
+ }
55
+ }
56
+ }
57
+
58
+ // Check Codex sessions
59
+ if (project.codexSessions && project.codexSessions.length > 0) {
60
+ for (const session of project.codexSessions) {
61
+ const sessionDate = new Date(session.lastActivity || session.createdAt);
62
+ if (!lastActivity || sessionDate > lastActivity) {
63
+ lastActivity = sessionDate;
64
+ }
65
+ }
66
+ }
67
+
68
+ return lastActivity ? lastActivity.toISOString() : null;
69
+ }
70
+
71
+ /**
72
+ * Transform full project data to slim format
73
+ */
74
+ function toSlimProject(project) {
75
+ const claudeCount = project.sessions?.length || 0;
76
+ const cursorCount = project.cursorSessions?.length || 0;
77
+ const codexCount = project.codexSessions?.length || 0;
78
+
79
+ return {
80
+ name: project.name,
81
+ displayName: project.displayName,
82
+ fullPath: project.fullPath || project.path,
83
+ sessionCount: claudeCount + cursorCount + codexCount,
84
+ lastActivity: calculateLastActivity(project),
85
+ hasClaudeSessions: claudeCount > 0,
86
+ hasCursorSessions: cursorCount > 0,
87
+ hasCodexSessions: codexCount > 0,
88
+ hasTaskmaster: project.taskmaster?.hasTaskmaster || false,
89
+ sessionMeta: project.sessionMeta,
90
+ isManuallyAdded: project.isManuallyAdded || false,
91
+ isCustomName: project.isCustomName || false,
92
+ };
93
+ }
94
+
95
+ /**
96
+ * Update the projects cache from full projects data
97
+ * Called after getProjects() completes
98
+ */
99
+ function updateProjectsCache(projects) {
100
+ // Transform to slim format
101
+ cachedProjects = projects.map(toSlimProject);
102
+
103
+ // Sort by lastActivity descending (most recent first)
104
+ cachedProjects.sort((a, b) => {
105
+ const dateA = a.lastActivity ? new Date(a.lastActivity) : new Date(0);
106
+ const dateB = b.lastActivity ? new Date(b.lastActivity) : new Date(0);
107
+ return dateB - dateA;
108
+ });
109
+
110
+ cacheVersion++;
111
+ cacheTimestamp = new Date().toISOString();
112
+ }
113
+
114
+ /**
115
+ * Get projects filtered by timeframe
116
+ * Projects are included if their lastActivity is within the timeframe
117
+ */
118
+ function getProjectsByTimeframe(timeframe = "1w") {
119
+ const now = Date.now();
120
+ const cutoffMs = TIMEFRAME_MS[timeframe] || TIMEFRAME_MS["1w"];
121
+
122
+ if (cutoffMs === Infinity) {
123
+ return {
124
+ projects: cachedProjects,
125
+ totalCount: cachedProjects.length,
126
+ filteredCount: cachedProjects.length,
127
+ };
128
+ }
129
+
130
+ const cutoffTime = now - cutoffMs;
131
+ const filteredProjects = cachedProjects.filter((project) => {
132
+ if (!project.lastActivity) {
133
+ return false; // Exclude projects with no sessions
134
+ }
135
+ const projectTime = new Date(project.lastActivity).getTime();
136
+ return projectTime >= cutoffTime;
137
+ });
138
+
139
+ return {
140
+ projects: filteredProjects,
141
+ totalCount: cachedProjects.length,
142
+ filteredCount: filteredProjects.length,
143
+ };
144
+ }
145
+
146
+ /**
147
+ * Generate ETag for current cache state + timeframe
148
+ */
149
+ function generateETag(timeframe = "1w") {
150
+ const hash = crypto.createHash("md5");
151
+ hash.update(`projects-${cacheVersion}-${cacheTimestamp}-${timeframe}`);
152
+ return `"${hash.digest("hex")}"`;
153
+ }
154
+
155
+ /**
156
+ * Get cache metadata
157
+ */
158
+ function getCacheMeta() {
159
+ return {
160
+ version: cacheVersion,
161
+ timestamp: cacheTimestamp,
162
+ projectCount: cachedProjects.length,
163
+ };
164
+ }
165
+
166
+ /**
167
+ * Check if cache is initialized
168
+ */
169
+ function isCacheInitialized() {
170
+ return cacheTimestamp !== null;
171
+ }
172
+
173
+ /**
174
+ * Get the raw cached projects (for initial load)
175
+ */
176
+ function getCachedProjects() {
177
+ return cachedProjects;
178
+ }
179
+
180
+ /**
181
+ * Get a single project from cache by name
182
+ */
183
+ function getProjectFromCache(projectName) {
184
+ return cachedProjects.find((p) => p.name === projectName) || null;
185
+ }
186
+
187
+ export {
188
+ updateProjectsCache,
189
+ getProjectsByTimeframe,
190
+ generateETag,
191
+ getCacheMeta,
192
+ isCacheInitialized,
193
+ getCachedProjects,
194
+ getProjectFromCache,
195
+ TIMEFRAME_MS,
196
+ };