claude-yes 1.31.2 → 1.32.2
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/README.md +225 -21
- package/dist/agent-yes.js +2 -0
- package/dist/amp-yes.js +2 -0
- package/dist/auggie-yes.js +2 -0
- package/dist/claude-yes.js +2 -20432
- package/dist/cli.js +18342 -10955
- package/dist/codex-yes.js +2 -20432
- package/dist/copilot-yes.js +2 -20432
- package/dist/cursor-yes.js +2 -20432
- package/dist/gemini-yes.js +2 -20432
- package/dist/grok-yes.js +2 -20432
- package/dist/index.js +16258 -13586
- package/dist/qwen-yes.js +2 -20432
- package/package.json +93 -81
- package/ts/ReadyManager.spec.ts +10 -10
- package/ts/ReadyManager.ts +1 -1
- package/ts/SUPPORTED_CLIS.ts +4 -0
- package/ts/catcher.spec.ts +69 -70
- package/ts/cli-idle.spec.ts +8 -8
- package/ts/cli.ts +18 -26
- package/ts/defineConfig.ts +4 -4
- package/ts/idleWaiter.spec.ts +9 -9
- package/ts/index.ts +474 -233
- package/ts/logger.ts +22 -0
- package/ts/parseCliArgs.spec.ts +146 -147
- package/ts/parseCliArgs.ts +127 -59
- package/ts/postbuild.ts +29 -15
- package/ts/pty-fix.ts +155 -0
- package/ts/pty.ts +19 -0
- package/ts/removeControlCharacters.spec.ts +37 -38
- package/ts/removeControlCharacters.ts +2 -1
- package/ts/runningLock.spec.ts +119 -125
- package/ts/runningLock.ts +44 -55
- package/ts/session-integration.spec.ts +34 -42
- package/ts/utils.spec.ts +35 -35
- package/ts/utils.ts +7 -7
- package/dist/cli.js.map +0 -365
- package/dist/index.js.map +0 -323
- package/ts/codex-resume.spec.ts +0 -239
- package/ts/codexSessionManager.spec.ts +0 -51
- package/ts/codexSessionManager.test.ts +0 -259
- package/ts/codexSessionManager.ts +0 -312
- package/ts/yesLog.spec.ts +0 -74
- package/ts/yesLog.ts +0 -27
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
import { mkdir, readdir, readFile, writeFile } from 'fs/promises';
|
|
2
|
-
import { homedir } from 'os';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
|
|
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');
|
|
20
|
-
|
|
21
|
-
export interface CodexSessionMap {
|
|
22
|
-
[cwd: string]: {
|
|
23
|
-
sessionId: string;
|
|
24
|
-
lastUsed: string; // ISO timestamp
|
|
25
|
-
};
|
|
26
|
-
}
|
|
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
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Load the session map from the config file
|
|
42
|
-
*/
|
|
43
|
-
export async function loadSessionMap(): Promise<CodexSessionMap> {
|
|
44
|
-
try {
|
|
45
|
-
const content = await readFile(getSessionsFile(), 'utf-8');
|
|
46
|
-
return JSON.parse(content);
|
|
47
|
-
} catch (error) {
|
|
48
|
-
// File doesn't exist or is invalid, return empty map
|
|
49
|
-
return {};
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Save the session map to the config file
|
|
55
|
-
*/
|
|
56
|
-
export async function saveSessionMap(
|
|
57
|
-
sessionMap: CodexSessionMap,
|
|
58
|
-
): Promise<void> {
|
|
59
|
-
try {
|
|
60
|
-
const sessionsFile = getSessionsFile();
|
|
61
|
-
// Ensure the directory exists
|
|
62
|
-
await mkdir(path.dirname(sessionsFile), { recursive: true });
|
|
63
|
-
await writeFile(sessionsFile, JSON.stringify(sessionMap, null, 2));
|
|
64
|
-
} catch (error) {
|
|
65
|
-
console.warn('Failed to save codex session map:', error);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Store a session ID for a specific working directory
|
|
71
|
-
*/
|
|
72
|
-
export async function storeSessionForCwd(
|
|
73
|
-
cwd: string,
|
|
74
|
-
sessionId: string,
|
|
75
|
-
): Promise<void> {
|
|
76
|
-
const sessionMap = await loadSessionMap();
|
|
77
|
-
sessionMap[cwd] = {
|
|
78
|
-
sessionId,
|
|
79
|
-
lastUsed: new Date().toISOString(),
|
|
80
|
-
};
|
|
81
|
-
await saveSessionMap(sessionMap);
|
|
82
|
-
}
|
|
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
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Get the last session ID for a specific working directory
|
|
190
|
-
* Now checks actual codex session files first, falls back to stored mapping
|
|
191
|
-
*/
|
|
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
|
|
200
|
-
const sessionMap = await loadSessionMap();
|
|
201
|
-
return sessionMap[cwd]?.sessionId || null;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Extract session ID from codex output
|
|
206
|
-
* Session IDs are UUIDs in the format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
|
207
|
-
*/
|
|
208
|
-
export function extractSessionId(output: string): string | null {
|
|
209
|
-
// Look for session ID in various contexts where it might appear
|
|
210
|
-
const sessionIdRegex =
|
|
211
|
-
/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i;
|
|
212
|
-
const match = output.match(sessionIdRegex);
|
|
213
|
-
return match ? match[0] : null;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Extract session ID from codex session file content
|
|
218
|
-
* More reliable method that parses the session metadata
|
|
219
|
-
*/
|
|
220
|
-
export function extractSessionIdFromSessionMeta(
|
|
221
|
-
sessionContent: string,
|
|
222
|
-
): string | null {
|
|
223
|
-
try {
|
|
224
|
-
// Parse the first line which should contain session metadata
|
|
225
|
-
const firstLine = sessionContent.split('\n')[0];
|
|
226
|
-
const sessionMeta = JSON.parse(firstLine);
|
|
227
|
-
|
|
228
|
-
if (sessionMeta.type === 'session_meta' && sessionMeta.payload?.id) {
|
|
229
|
-
return sessionMeta.payload.id;
|
|
230
|
-
}
|
|
231
|
-
} catch (error) {
|
|
232
|
-
// If parsing fails, fall back to regex extraction
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
return extractSessionId(sessionContent);
|
|
236
|
-
}
|
|
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
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Clean up old sessions (keep only the most recent 10 per directory)
|
|
286
|
-
*/
|
|
287
|
-
export async function cleanupOldSessions(): Promise<void> {
|
|
288
|
-
const sessionMap = await loadSessionMap();
|
|
289
|
-
|
|
290
|
-
// Group sessions by directory and keep only the most recent ones
|
|
291
|
-
const cleaned: CodexSessionMap = {};
|
|
292
|
-
|
|
293
|
-
// Sort all sessions by lastUsed date (most recent first)
|
|
294
|
-
const sortedEntries = Object.entries(sessionMap).sort(
|
|
295
|
-
([, a], [, b]) =>
|
|
296
|
-
new Date(b.lastUsed).getTime() - new Date(a.lastUsed).getTime(),
|
|
297
|
-
);
|
|
298
|
-
|
|
299
|
-
// Keep track of how many sessions we've kept per directory
|
|
300
|
-
const dirCounts: { [dir: string]: number } = {};
|
|
301
|
-
|
|
302
|
-
for (const [cwd, session] of sortedEntries) {
|
|
303
|
-
const count = dirCounts[cwd] || 0;
|
|
304
|
-
if (count < 5) {
|
|
305
|
-
// Keep up to 5 sessions per directory
|
|
306
|
-
cleaned[cwd] = session;
|
|
307
|
-
dirCounts[cwd] = count + 1;
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
await saveSessionMap(cleaned);
|
|
312
|
-
}
|
package/ts/yesLog.spec.ts
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
describe('yesLog', () => {
|
|
4
|
-
const originalVerbose = process.env.VERBOSE;
|
|
5
|
-
|
|
6
|
-
beforeEach(() => {
|
|
7
|
-
// Reset modules to ensure fresh state
|
|
8
|
-
delete require.cache[require.resolve('./yesLog')];
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
afterEach(() => {
|
|
12
|
-
// Restore original VERBOSE setting
|
|
13
|
-
if (originalVerbose !== undefined) {
|
|
14
|
-
process.env.VERBOSE = originalVerbose;
|
|
15
|
-
} else {
|
|
16
|
-
delete process.env.VERBOSE;
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('should not crash when VERBOSE is not set', async () => {
|
|
21
|
-
delete process.env.VERBOSE;
|
|
22
|
-
|
|
23
|
-
const { yesLog } = await import('./yesLog');
|
|
24
|
-
|
|
25
|
-
// Should not throw and returns undefined
|
|
26
|
-
const result = yesLog`Test message`;
|
|
27
|
-
expect(result).toBeUndefined();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('should be callable with template literals', async () => {
|
|
31
|
-
delete process.env.VERBOSE;
|
|
32
|
-
|
|
33
|
-
const { yesLog } = await import('./yesLog');
|
|
34
|
-
|
|
35
|
-
// Should not throw with variables
|
|
36
|
-
const variable = 'test value';
|
|
37
|
-
const result = yesLog`Message with ${variable}`;
|
|
38
|
-
expect(result).toBeUndefined();
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('should handle multiple calls', async () => {
|
|
42
|
-
delete process.env.VERBOSE;
|
|
43
|
-
|
|
44
|
-
const { yesLog } = await import('./yesLog');
|
|
45
|
-
|
|
46
|
-
// Multiple calls should not throw
|
|
47
|
-
expect(yesLog`First message`).toBeUndefined();
|
|
48
|
-
expect(yesLog`Second message`).toBeUndefined();
|
|
49
|
-
expect(yesLog`Third message`).toBeUndefined();
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('should work when VERBOSE is set', async () => {
|
|
53
|
-
process.env.VERBOSE = '1';
|
|
54
|
-
|
|
55
|
-
const { yesLog } = await import('./yesLog');
|
|
56
|
-
|
|
57
|
-
// Should not throw even when verbose
|
|
58
|
-
expect(yesLog`Verbose message`).toBeUndefined();
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('should handle template literals with different types', async () => {
|
|
62
|
-
delete process.env.VERBOSE;
|
|
63
|
-
|
|
64
|
-
const { yesLog } = await import('./yesLog');
|
|
65
|
-
|
|
66
|
-
const number = 42;
|
|
67
|
-
const object = { key: 'value' };
|
|
68
|
-
const array = [1, 2, 3];
|
|
69
|
-
|
|
70
|
-
expect(yesLog`Number: ${number}`).toBeUndefined();
|
|
71
|
-
expect(yesLog`Object: ${object}`).toBeUndefined();
|
|
72
|
-
expect(yesLog`Array: ${array}`).toBeUndefined();
|
|
73
|
-
});
|
|
74
|
-
});
|
package/ts/yesLog.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { appendFileSync, rmSync } from 'node:fs';
|
|
2
|
-
import tsaComposer from 'tsa-composer';
|
|
3
|
-
import { catcher } from './catcher';
|
|
4
|
-
|
|
5
|
-
let initial = true;
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Log messages to agent-yes.log file
|
|
9
|
-
* Each message is appended as a new line
|
|
10
|
-
* The log file is cleared on the first call
|
|
11
|
-
*
|
|
12
|
-
* use only for debug, enabled when process.env.VERBOSE is set
|
|
13
|
-
*/
|
|
14
|
-
export const yesLog = tsaComposer()(
|
|
15
|
-
catcher(
|
|
16
|
-
(error) => {
|
|
17
|
-
console.error('yesLog error:', error);
|
|
18
|
-
},
|
|
19
|
-
function yesLog(msg: string) {
|
|
20
|
-
// process.stdout.write(`${msg}\r`); // touch process to avoid "The process is not running a TTY." error
|
|
21
|
-
if (!process.env.VERBOSE) return; // no-op if not verbose
|
|
22
|
-
if (initial) rmSync('./agent-yes.log'); // ignore error if file doesn't exist
|
|
23
|
-
initial = false;
|
|
24
|
-
appendFileSync('./agent-yes.log', `${msg}\n`);
|
|
25
|
-
},
|
|
26
|
-
),
|
|
27
|
-
);
|