claude-yes 1.30.0 → 1.31.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.
- package/dist/claude-yes.js +82 -11
- package/dist/cli.js +82 -11
- package/dist/cli.js.map +4 -4
- package/dist/codex-yes.js +82 -11
- package/dist/copilot-yes.js +82 -11
- package/dist/cursor-yes.js +82 -11
- package/dist/gemini-yes.js +82 -11
- package/dist/grok-yes.js +82 -11
- package/dist/index.js +79 -6
- package/dist/index.js.map +3 -3
- package/dist/qwen-yes.js +82 -11
- package/package.json +9 -4
- package/ts/cli.ts +3 -3
- package/ts/codex-resume.spec.ts +0 -4
- package/ts/codexSessionManager.test.ts +259 -0
- package/ts/codexSessionManager.ts +190 -10
|
@@ -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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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(
|
|
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(
|
|
41
|
-
await writeFile(
|
|
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
|
*/
|