claude-yes 1.30.0 → 1.31.1

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.
@@ -1,13 +1,22 @@
1
- import { mkdir, readFile, writeFile } from 'fs/promises';
1
+ import { mkdir, readdir, readFile, writeFile } from 'fs/promises';
2
2
  import { homedir } from 'os';
3
3
  import path from 'path';
4
4
 
5
- const SESSIONS_FILE = path.join(
6
- homedir(),
7
- '.config',
8
- 'cli-yes',
9
- 'codex-sessions.json',
10
- );
5
+ // Allow overriding for testing
6
+ export const getSessionsFile = () =>
7
+ process.env.CLI_YES_TEST_HOME
8
+ ? path.join(
9
+ process.env.CLI_YES_TEST_HOME,
10
+ '.config',
11
+ 'cli-yes',
12
+ 'codex-sessions.json',
13
+ )
14
+ : path.join(homedir(), '.config', 'cli-yes', 'codex-sessions.json');
15
+
16
+ export const getCodexSessionsDir = () =>
17
+ process.env.CLI_YES_TEST_HOME
18
+ ? path.join(process.env.CLI_YES_TEST_HOME, '.codex', 'sessions')
19
+ : path.join(homedir(), '.codex', 'sessions');
11
20
 
12
21
  export interface CodexSessionMap {
13
22
  [cwd: string]: {
@@ -16,12 +25,24 @@ export interface CodexSessionMap {
16
25
  };
17
26
  }
18
27
 
28
+ export interface CodexSession {
29
+ id: string;
30
+ timestamp: string;
31
+ cwd: string;
32
+ filePath: string;
33
+ git?: {
34
+ commit_hash: string;
35
+ branch: string;
36
+ repository_url: string;
37
+ };
38
+ }
39
+
19
40
  /**
20
41
  * Load the session map from the config file
21
42
  */
22
43
  export async function loadSessionMap(): Promise<CodexSessionMap> {
23
44
  try {
24
- const content = await readFile(SESSIONS_FILE, 'utf-8');
45
+ const content = await readFile(getSessionsFile(), 'utf-8');
25
46
  return JSON.parse(content);
26
47
  } catch (error) {
27
48
  // File doesn't exist or is invalid, return empty map
@@ -36,9 +57,10 @@ export async function saveSessionMap(
36
57
  sessionMap: CodexSessionMap,
37
58
  ): Promise<void> {
38
59
  try {
60
+ const sessionsFile = getSessionsFile();
39
61
  // Ensure the directory exists
40
- await mkdir(path.dirname(SESSIONS_FILE), { recursive: true });
41
- await writeFile(SESSIONS_FILE, JSON.stringify(sessionMap, null, 2));
62
+ await mkdir(path.dirname(sessionsFile), { recursive: true });
63
+ await writeFile(sessionsFile, JSON.stringify(sessionMap, null, 2));
42
64
  } catch (error) {
43
65
  console.warn('Failed to save codex session map:', error);
44
66
  }
@@ -59,10 +81,122 @@ export async function storeSessionForCwd(
59
81
  await saveSessionMap(sessionMap);
60
82
  }
61
83
 
84
+ /**
85
+ * Parse a codex session file to extract session metadata
86
+ */
87
+ async function parseCodexSessionFile(
88
+ filePath: string,
89
+ ): Promise<CodexSession | null> {
90
+ try {
91
+ const content = await readFile(filePath, 'utf-8');
92
+ const lines = content.trim().split('\n');
93
+
94
+ // Find the session_meta line
95
+ for (const line of lines) {
96
+ if (!line.trim()) continue;
97
+
98
+ const data = JSON.parse(line);
99
+ if (data.type === 'session_meta' && data.payload) {
100
+ const payload = data.payload;
101
+ return {
102
+ id: payload.id,
103
+ timestamp: payload.timestamp || data.timestamp,
104
+ cwd: payload.cwd,
105
+ filePath,
106
+ git: payload.git,
107
+ };
108
+ }
109
+ }
110
+
111
+ return null;
112
+ } catch (error) {
113
+ // Ignore files that can't be parsed
114
+ return null;
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Get all codex sessions from the .codex/sessions directory
120
+ */
121
+ async function getAllCodexSessions(): Promise<CodexSession[]> {
122
+ const sessions: CodexSession[] = [];
123
+ const codexSessionsDir = getCodexSessionsDir();
124
+
125
+ try {
126
+ // Walk through year/month/day structure
127
+ const years = await readdir(codexSessionsDir);
128
+
129
+ for (const year of years) {
130
+ const yearPath = path.join(codexSessionsDir, year);
131
+ try {
132
+ const months = await readdir(yearPath);
133
+
134
+ for (const month of months) {
135
+ const monthPath = path.join(yearPath, month);
136
+ try {
137
+ const days = await readdir(monthPath);
138
+
139
+ for (const day of days) {
140
+ const dayPath = path.join(monthPath, day);
141
+ try {
142
+ const files = await readdir(dayPath);
143
+
144
+ for (const file of files) {
145
+ if (file.endsWith('.jsonl')) {
146
+ const sessionPath = path.join(dayPath, file);
147
+ const session = await parseCodexSessionFile(sessionPath);
148
+ if (session) {
149
+ sessions.push(session);
150
+ }
151
+ }
152
+ }
153
+ } catch (error) {
154
+ // Skip directories we can't read
155
+ }
156
+ }
157
+ } catch (error) {
158
+ // Skip directories we can't read
159
+ }
160
+ }
161
+ } catch (error) {
162
+ // Skip directories we can't read
163
+ }
164
+ }
165
+ } catch (error) {
166
+ // .codex/sessions directory doesn't exist or can't be read
167
+ return [];
168
+ }
169
+
170
+ return sessions.sort(
171
+ (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(),
172
+ );
173
+ }
174
+
175
+ /**
176
+ * Get the most recent session for a specific working directory from actual codex files
177
+ */
178
+ async function getMostRecentCodexSessionForCwd(
179
+ targetCwd: string,
180
+ ): Promise<CodexSession | null> {
181
+ const allSessions = await getAllCodexSessions();
182
+ const sessionsForCwd = allSessions.filter(
183
+ (session) => session.cwd === targetCwd,
184
+ );
185
+ return sessionsForCwd[0] || null;
186
+ }
187
+
62
188
  /**
63
189
  * Get the last session ID for a specific working directory
190
+ * Now checks actual codex session files first, falls back to stored mapping
64
191
  */
65
192
  export async function getSessionForCwd(cwd: string): Promise<string | null> {
193
+ // First try to get the most recent session from actual codex files
194
+ const recentSession = await getMostRecentCodexSessionForCwd(cwd);
195
+ if (recentSession) {
196
+ return recentSession.id;
197
+ }
198
+
199
+ // Fall back to stored mapping
66
200
  const sessionMap = await loadSessionMap();
67
201
  return sessionMap[cwd]?.sessionId || null;
68
202
  }
@@ -101,6 +235,52 @@ export function extractSessionIdFromSessionMeta(
101
235
  return extractSessionId(sessionContent);
102
236
  }
103
237
 
238
+ /**
239
+ * Get recent sessions for a specific working directory from actual codex files
240
+ */
241
+ export async function getRecentSessionsForCwd(
242
+ targetCwd: string,
243
+ limit = 5,
244
+ ): Promise<CodexSession[]> {
245
+ const allSessions = await getAllCodexSessions();
246
+ const sessionsForCwd = allSessions.filter(
247
+ (session) => session.cwd === targetCwd,
248
+ );
249
+ return sessionsForCwd.slice(0, limit);
250
+ }
251
+
252
+ /**
253
+ * Get all working directories with session counts from actual codex files
254
+ */
255
+ export async function getAllWorkingDirectories(): Promise<
256
+ { cwd: string; count: number; lastSession: string }[]
257
+ > {
258
+ const allSessions = await getAllCodexSessions();
259
+ const cwdMap = new Map<string, { count: number; lastSession: string }>();
260
+
261
+ for (const session of allSessions) {
262
+ const existing = cwdMap.get(session.cwd);
263
+ if (existing) {
264
+ existing.count++;
265
+ if (new Date(session.timestamp) > new Date(existing.lastSession)) {
266
+ existing.lastSession = session.timestamp;
267
+ }
268
+ } else {
269
+ cwdMap.set(session.cwd, {
270
+ count: 1,
271
+ lastSession: session.timestamp,
272
+ });
273
+ }
274
+ }
275
+
276
+ return Array.from(cwdMap.entries())
277
+ .map(([cwd, data]) => ({ cwd, ...data }))
278
+ .sort(
279
+ (a, b) =>
280
+ new Date(b.lastSession).getTime() - new Date(a.lastSession).getTime(),
281
+ );
282
+ }
283
+
104
284
  /**
105
285
  * Clean up old sessions (keep only the most recent 10 per directory)
106
286
  */