claude-yes 1.25.0 → 1.27.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 +644 -11971
- package/dist/cli.js +644 -11971
- package/dist/cli.js.map +8 -164
- package/dist/codex-yes.js +644 -11971
- package/dist/copilot-yes.js +644 -11971
- package/dist/cursor-yes.js +644 -11971
- package/dist/gemini-yes.js +644 -11971
- package/dist/grok-yes.js +644 -11971
- package/dist/index.js +94 -5262
- package/dist/index.js.map +7 -123
- package/dist/qwen-yes.js +644 -11971
- package/package.json +9 -10
- package/ts/cli.ts +11 -113
- package/ts/codex-resume.spec.ts +243 -0
- package/ts/codexSessionManager.spec.ts +51 -0
- package/ts/codexSessionManager.ts +132 -0
- package/ts/index.ts +48 -2
- package/ts/parseCliArgs.spec.ts +232 -0
- package/ts/parseCliArgs.ts +114 -0
- package/ts/session-integration.spec.ts +101 -0
- package/ts/cli.spec.ts +0 -18
package/ts/index.ts
CHANGED
|
@@ -6,6 +6,11 @@ import sflow from 'sflow';
|
|
|
6
6
|
import { TerminalTextRender } from 'terminal-render';
|
|
7
7
|
import tsaComposer from 'tsa-composer';
|
|
8
8
|
import rawConfig from '../cli-yes.config.js';
|
|
9
|
+
import {
|
|
10
|
+
extractSessionId,
|
|
11
|
+
getSessionForCwd,
|
|
12
|
+
storeSessionForCwd,
|
|
13
|
+
} from './codexSessionManager.js';
|
|
9
14
|
import { defineCliYesConfig } from './defineConfig.js';
|
|
10
15
|
import { IdleWaiter } from './idleWaiter';
|
|
11
16
|
import { ReadyManager } from './ReadyManager';
|
|
@@ -171,6 +176,25 @@ export default async function cliYes({
|
|
|
171
176
|
? [...cliConf.defaultArgs, ...cliArgs]
|
|
172
177
|
: cliArgs;
|
|
173
178
|
|
|
179
|
+
// Handle --continue flag for codex session restoration
|
|
180
|
+
const continueIndex = cliArgs.indexOf('--continue');
|
|
181
|
+
if (continueIndex !== -1 && cli === 'codex') {
|
|
182
|
+
// Remove the --continue flag from args
|
|
183
|
+
cliArgs.splice(continueIndex, 1);
|
|
184
|
+
|
|
185
|
+
// Try to get stored session for this directory
|
|
186
|
+
const storedSessionId = await getSessionForCwd(workingDir);
|
|
187
|
+
if (storedSessionId) {
|
|
188
|
+
// Replace or add resume args
|
|
189
|
+
cliArgs = ['resume', storedSessionId, ...cliArgs];
|
|
190
|
+
await yesLog`continue|using stored session ID: ${storedSessionId}`;
|
|
191
|
+
} else {
|
|
192
|
+
// Fallback to --last if no stored session
|
|
193
|
+
cliArgs = ['resume', '--last', ...cliArgs];
|
|
194
|
+
await yesLog`continue|no stored session, using --last`;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
174
198
|
if (prompt && cliConf.promptArg) {
|
|
175
199
|
if (cliConf.promptArg === 'first-arg') {
|
|
176
200
|
cliArgs = [prompt, ...cliArgs];
|
|
@@ -221,7 +245,7 @@ export default async function cliYes({
|
|
|
221
245
|
}
|
|
222
246
|
|
|
223
247
|
shell.onData(onData);
|
|
224
|
-
shell.onExit(function onExit({ exitCode }) {
|
|
248
|
+
shell.onExit(async function onExit({ exitCode }) {
|
|
225
249
|
stdinReady.unready(); // start buffer stdin
|
|
226
250
|
const agentCrashed = exitCode !== 0;
|
|
227
251
|
|
|
@@ -240,7 +264,20 @@ export default async function cliYes({
|
|
|
240
264
|
|
|
241
265
|
console.log(`${cli} crashed, restarting...`);
|
|
242
266
|
|
|
243
|
-
|
|
267
|
+
// For codex, try to use stored session ID for this directory
|
|
268
|
+
let restoreArgs = conf.restoreArgs;
|
|
269
|
+
if (cli === 'codex') {
|
|
270
|
+
const storedSessionId = await getSessionForCwd(workingDir);
|
|
271
|
+
if (storedSessionId) {
|
|
272
|
+
// Use specific session ID instead of --last
|
|
273
|
+
restoreArgs = ['resume', storedSessionId];
|
|
274
|
+
await yesLog`restore|using stored session ID: ${storedSessionId}`;
|
|
275
|
+
} else {
|
|
276
|
+
await yesLog`restore|no stored session, using default restore args`;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
shell = pty.spawn(cli, restoreArgs, getPtyOptions());
|
|
244
281
|
shell.onData(onData);
|
|
245
282
|
shell.onExit(onExit);
|
|
246
283
|
return;
|
|
@@ -346,6 +383,15 @@ export default async function cliYes({
|
|
|
346
383
|
isFatal = true;
|
|
347
384
|
await exitAgent();
|
|
348
385
|
}
|
|
386
|
+
|
|
387
|
+
// session ID capture for codex
|
|
388
|
+
if (cli === 'codex') {
|
|
389
|
+
const sessionId = extractSessionId(e);
|
|
390
|
+
if (sessionId) {
|
|
391
|
+
await yesLog`session|captured session ID: ${sessionId}`;
|
|
392
|
+
await storeSessionForCwd(workingDir, sessionId);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
349
395
|
})
|
|
350
396
|
.run(),
|
|
351
397
|
)
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { parseCliArgs } from './parseCliArgs';
|
|
3
|
+
|
|
4
|
+
describe('CLI argument parsing', () => {
|
|
5
|
+
it('should parse cli name from first positional argument', () => {
|
|
6
|
+
const result = parseCliArgs(['node', '/path/to/cli', 'claude']);
|
|
7
|
+
|
|
8
|
+
expect(result.cli).toBe('claude');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should parse prompt from --prompt flag', () => {
|
|
12
|
+
const result = parseCliArgs([
|
|
13
|
+
'node',
|
|
14
|
+
'/path/to/cli',
|
|
15
|
+
'--prompt',
|
|
16
|
+
'hello world',
|
|
17
|
+
'claude',
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
expect(result.prompt).toBe('hello world');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should parse prompt from -- separator', () => {
|
|
24
|
+
const result = parseCliArgs([
|
|
25
|
+
'node',
|
|
26
|
+
'/path/to/cli',
|
|
27
|
+
'claude',
|
|
28
|
+
'--',
|
|
29
|
+
'hello',
|
|
30
|
+
'world',
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
expect(result.prompt).toBe('hello world');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should parse prompt from only -- separator with -yes cli', () => {
|
|
37
|
+
const result = parseCliArgs([
|
|
38
|
+
'node',
|
|
39
|
+
'/path/to/claude-yes',
|
|
40
|
+
'--',
|
|
41
|
+
'hello',
|
|
42
|
+
'world',
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
expect(result.prompt).toBe('hello world');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should combine --prompt and -- prompt', () => {
|
|
49
|
+
const result = parseCliArgs([
|
|
50
|
+
'node',
|
|
51
|
+
'/path/to/cli',
|
|
52
|
+
'--prompt',
|
|
53
|
+
'part1',
|
|
54
|
+
'claude',
|
|
55
|
+
'--',
|
|
56
|
+
'part2',
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
expect(result.prompt).toBe('part1 part2');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should parse --idle flag', () => {
|
|
63
|
+
const result = parseCliArgs([
|
|
64
|
+
'node',
|
|
65
|
+
'/path/to/cli',
|
|
66
|
+
'--idle',
|
|
67
|
+
'30s',
|
|
68
|
+
'claude',
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
expect(result.exitOnIdle).toBe(30000);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should parse --exit-on-idle flag (deprecated)', () => {
|
|
75
|
+
const result = parseCliArgs([
|
|
76
|
+
'node',
|
|
77
|
+
'/path/to/cli',
|
|
78
|
+
'--exit-on-idle',
|
|
79
|
+
'1m',
|
|
80
|
+
'claude',
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
expect(result.exitOnIdle).toBe(60000);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should parse --robust flag', () => {
|
|
87
|
+
const result = parseCliArgs(['node', '/path/to/cli', '--robust', 'claude']);
|
|
88
|
+
|
|
89
|
+
expect(result.robust).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should parse --no-robust flag', () => {
|
|
93
|
+
const result = parseCliArgs([
|
|
94
|
+
'node',
|
|
95
|
+
'/path/to/cli',
|
|
96
|
+
'--no-robust',
|
|
97
|
+
'claude',
|
|
98
|
+
]);
|
|
99
|
+
|
|
100
|
+
expect(result.robust).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should parse --queue flag', () => {
|
|
104
|
+
const result = parseCliArgs(['node', '/path/to/cli', '--queue', 'claude']);
|
|
105
|
+
|
|
106
|
+
expect(result.queue).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should parse --no-queue flag', () => {
|
|
110
|
+
const result = parseCliArgs([
|
|
111
|
+
'node',
|
|
112
|
+
'/path/to/cli',
|
|
113
|
+
'--no-queue',
|
|
114
|
+
'claude',
|
|
115
|
+
]);
|
|
116
|
+
|
|
117
|
+
expect(result.queue).toBe(false);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should parse --logFile flag', () => {
|
|
121
|
+
const result = parseCliArgs([
|
|
122
|
+
'node',
|
|
123
|
+
'/path/to/cli',
|
|
124
|
+
'--logFile',
|
|
125
|
+
'./output.log',
|
|
126
|
+
'claude',
|
|
127
|
+
]);
|
|
128
|
+
|
|
129
|
+
expect(result.logFile).toBe('./output.log');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should parse --verbose flag', () => {
|
|
133
|
+
const result = parseCliArgs([
|
|
134
|
+
'node',
|
|
135
|
+
'/path/to/cli',
|
|
136
|
+
'--verbose',
|
|
137
|
+
'claude',
|
|
138
|
+
]);
|
|
139
|
+
|
|
140
|
+
expect(result.verbose).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should pass through unknown CLI args to cliArgs', () => {
|
|
144
|
+
const result = parseCliArgs([
|
|
145
|
+
'node',
|
|
146
|
+
'/path/to/cli',
|
|
147
|
+
'claude',
|
|
148
|
+
'--unknown-flag',
|
|
149
|
+
'value',
|
|
150
|
+
]);
|
|
151
|
+
|
|
152
|
+
expect(result.cliArgs).toContain('--unknown-flag');
|
|
153
|
+
expect(result.cliArgs).toContain('value');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should separate cli-yes args from cli args before --', () => {
|
|
157
|
+
const result = parseCliArgs([
|
|
158
|
+
'node',
|
|
159
|
+
'/path/to/cli',
|
|
160
|
+
'--robust',
|
|
161
|
+
'claude',
|
|
162
|
+
'--claude-arg',
|
|
163
|
+
'--',
|
|
164
|
+
'prompt',
|
|
165
|
+
]);
|
|
166
|
+
|
|
167
|
+
expect(result.cli).toBe('claude');
|
|
168
|
+
expect(result.robust).toBe(true);
|
|
169
|
+
expect(result.cliArgs).toContain('--claude-arg');
|
|
170
|
+
expect(result.prompt).toBe('prompt');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should detect cli name from script name (claude-yes)', () => {
|
|
174
|
+
const result = parseCliArgs([
|
|
175
|
+
'/usr/bin/node',
|
|
176
|
+
'/usr/local/bin/claude-yes',
|
|
177
|
+
'--prompt',
|
|
178
|
+
'test',
|
|
179
|
+
]);
|
|
180
|
+
|
|
181
|
+
expect(result.cli).toBe('claude');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should detect cli name from script name (codex-yes)', () => {
|
|
185
|
+
const result = parseCliArgs([
|
|
186
|
+
'/usr/bin/node',
|
|
187
|
+
'/usr/local/bin/codex-yes',
|
|
188
|
+
'--prompt',
|
|
189
|
+
'test',
|
|
190
|
+
]);
|
|
191
|
+
|
|
192
|
+
expect(result.cli).toBe('codex');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('should prefer script name over explicit cli argument', () => {
|
|
196
|
+
const result = parseCliArgs([
|
|
197
|
+
'/usr/bin/node',
|
|
198
|
+
'/usr/local/bin/claude-yes',
|
|
199
|
+
'--prompt',
|
|
200
|
+
'test',
|
|
201
|
+
'gemini',
|
|
202
|
+
]);
|
|
203
|
+
|
|
204
|
+
// cliName (from script) takes precedence over positional arg
|
|
205
|
+
expect(result.cli).toBe('claude');
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should handle empty cliArgs when no positional cli is provided', () => {
|
|
209
|
+
const result = parseCliArgs([
|
|
210
|
+
'/usr/bin/node',
|
|
211
|
+
'/usr/local/bin/claude-yes',
|
|
212
|
+
'--prompt',
|
|
213
|
+
'prompt',
|
|
214
|
+
]);
|
|
215
|
+
|
|
216
|
+
expect(result.cliArgs).toEqual([]);
|
|
217
|
+
expect(result.prompt).toBe('prompt');
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should include all args when no -- separator is present', () => {
|
|
221
|
+
const result = parseCliArgs([
|
|
222
|
+
'node',
|
|
223
|
+
'/path/to/cli',
|
|
224
|
+
'claude',
|
|
225
|
+
'--some-flag',
|
|
226
|
+
'--another-flag',
|
|
227
|
+
]);
|
|
228
|
+
|
|
229
|
+
expect(result.cliArgs).toContain('--some-flag');
|
|
230
|
+
expect(result.cliArgs).toContain('--another-flag');
|
|
231
|
+
});
|
|
232
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import enhancedMs from 'enhanced-ms';
|
|
2
|
+
import yargs from 'yargs';
|
|
3
|
+
import { hideBin } from 'yargs/helpers';
|
|
4
|
+
import { SUPPORTED_CLIS } from '.';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Parse CLI arguments the same way cli.ts does
|
|
8
|
+
* This is a test helper that mirrors the parsing logic in cli.ts
|
|
9
|
+
*/
|
|
10
|
+
export function parseCliArgs(argv: string[]) {
|
|
11
|
+
// Detect cli name from script name (same logic as cli.ts:10-14)
|
|
12
|
+
const scriptName = argv[1]?.split(/[\/\\]/).pop();
|
|
13
|
+
const cliName = ((e?: string) => {
|
|
14
|
+
if (e === 'cli' || e === 'cli.ts' || e === 'cli.js') return undefined;
|
|
15
|
+
return e?.replace(/-yes$/, '');
|
|
16
|
+
})(scriptName);
|
|
17
|
+
|
|
18
|
+
// Parse args with yargs (same logic as cli.ts:16-73)
|
|
19
|
+
const parsedArgv = yargs(hideBin(argv))
|
|
20
|
+
.usage('Usage: $0 [cli] [cli-yes args] [agent-cli args] [--] [prompts...]')
|
|
21
|
+
.example(
|
|
22
|
+
'$0 claude --idle=30s -- solve all todos in my codebase, commit one by one',
|
|
23
|
+
'Run Claude with a 30 seconds idle timeout, and the prompt is everything after `--`',
|
|
24
|
+
)
|
|
25
|
+
.option('robust', {
|
|
26
|
+
type: 'boolean',
|
|
27
|
+
default: true,
|
|
28
|
+
description:
|
|
29
|
+
're-spawn Claude with --continue if it crashes, only works for claude yet',
|
|
30
|
+
alias: 'r',
|
|
31
|
+
})
|
|
32
|
+
.option('logFile', {
|
|
33
|
+
type: 'string',
|
|
34
|
+
description: 'Rendered log file to write to.',
|
|
35
|
+
})
|
|
36
|
+
.option('prompt', {
|
|
37
|
+
type: 'string',
|
|
38
|
+
description: 'Prompt to send to Claude (also can be passed after --)',
|
|
39
|
+
alias: 'p',
|
|
40
|
+
})
|
|
41
|
+
.option('verbose', {
|
|
42
|
+
type: 'boolean',
|
|
43
|
+
description: 'Enable verbose logging, will emit ./agent-yes.log',
|
|
44
|
+
default: false,
|
|
45
|
+
})
|
|
46
|
+
.option('exit-on-idle', {
|
|
47
|
+
type: 'string',
|
|
48
|
+
description: 'Exit after a period of inactivity, e.g., "5s" or "1m"',
|
|
49
|
+
deprecated: 'use --idle instead',
|
|
50
|
+
default: '60s',
|
|
51
|
+
alias: 'e',
|
|
52
|
+
})
|
|
53
|
+
.option('idle', {
|
|
54
|
+
type: 'string',
|
|
55
|
+
description: 'Exit after a period of inactivity, e.g., "5s" or "1m"',
|
|
56
|
+
alias: 'i',
|
|
57
|
+
})
|
|
58
|
+
.option('queue', {
|
|
59
|
+
type: 'boolean',
|
|
60
|
+
description:
|
|
61
|
+
'Queue Agent when spawning multiple agents in the same directory/repo, can be disabled with --no-queue',
|
|
62
|
+
default: true,
|
|
63
|
+
})
|
|
64
|
+
.positional('cli', {
|
|
65
|
+
describe:
|
|
66
|
+
'The AI CLI to run, e.g., claude, codex, copilot, cursor, gemini',
|
|
67
|
+
type: 'string',
|
|
68
|
+
choices: SUPPORTED_CLIS,
|
|
69
|
+
demandOption: false,
|
|
70
|
+
default: cliName,
|
|
71
|
+
})
|
|
72
|
+
.help()
|
|
73
|
+
.version()
|
|
74
|
+
.parserConfiguration({
|
|
75
|
+
'unknown-options-as-args': true,
|
|
76
|
+
'halt-at-non-option': true,
|
|
77
|
+
})
|
|
78
|
+
.parseSync();
|
|
79
|
+
|
|
80
|
+
// Extract cli args and dash prompt (same logic as cli.ts:76-91)
|
|
81
|
+
const optionalIndex = (e: number) => (0 <= e ? e : undefined);
|
|
82
|
+
const rawArgs = argv.slice(2);
|
|
83
|
+
const cliArgIndex = optionalIndex(rawArgs.indexOf(String(parsedArgv._[0])));
|
|
84
|
+
const dashIndex = optionalIndex(rawArgs.indexOf('--'));
|
|
85
|
+
|
|
86
|
+
const cliArgsForSpawn = parsedArgv._[0]
|
|
87
|
+
? rawArgs.slice(cliArgIndex ?? 0, dashIndex ?? undefined)
|
|
88
|
+
: [];
|
|
89
|
+
const dashPrompt: string | undefined =
|
|
90
|
+
dashIndex !== undefined
|
|
91
|
+
? rawArgs.slice(dashIndex + 1).join(' ')
|
|
92
|
+
: undefined;
|
|
93
|
+
|
|
94
|
+
// Return the config object that would be passed to cliYes (same logic as cli.ts:99-121)
|
|
95
|
+
return {
|
|
96
|
+
cli: (cliName ||
|
|
97
|
+
parsedArgv.cli ||
|
|
98
|
+
parsedArgv._[0]
|
|
99
|
+
?.toString()
|
|
100
|
+
?.replace?.(/-yes$/, '')) as (typeof SUPPORTED_CLIS)[number],
|
|
101
|
+
cliArgs: cliArgsForSpawn,
|
|
102
|
+
prompt:
|
|
103
|
+
[parsedArgv.prompt, dashPrompt].filter(Boolean).join(' ') || undefined,
|
|
104
|
+
exitOnIdle: Number(
|
|
105
|
+
(parsedArgv.idle || parsedArgv.exitOnIdle)?.replace(/.*/, (e) =>
|
|
106
|
+
String(enhancedMs(e)),
|
|
107
|
+
) || 0,
|
|
108
|
+
),
|
|
109
|
+
queue: parsedArgv.queue,
|
|
110
|
+
robust: parsedArgv.robust,
|
|
111
|
+
logFile: parsedArgv.logFile,
|
|
112
|
+
verbose: parsedArgv.verbose,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
import {
|
|
3
|
+
extractSessionId,
|
|
4
|
+
extractSessionIdFromSessionMeta,
|
|
5
|
+
} from './codexSessionManager';
|
|
6
|
+
|
|
7
|
+
describe('Session Extraction Test', () => {
|
|
8
|
+
it('should extract session IDs from various codex output formats', async () => {
|
|
9
|
+
console.log('\n=== Session ID Extraction Test ===');
|
|
10
|
+
|
|
11
|
+
// Test different formats where session IDs might appear
|
|
12
|
+
const testCases = [
|
|
13
|
+
{
|
|
14
|
+
name: 'Direct UUID in output',
|
|
15
|
+
output: 'Session started with ID: 0199e659-0e5f-7843-8876-5a65c64e77c0',
|
|
16
|
+
expected: '0199e659-0e5f-7843-8876-5a65c64e77c0',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: 'UUID in brackets',
|
|
20
|
+
output:
|
|
21
|
+
'Using session [0199e659-0e5f-7843-8876-5a65c64e77c0] for this conversation',
|
|
22
|
+
expected: '0199e659-0e5f-7843-8876-5a65c64e77c0',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'Mixed case UUID',
|
|
26
|
+
output: 'SESSION_ID: 0199E659-0E5F-7843-8876-5A65C64E77C0',
|
|
27
|
+
expected: '0199E659-0E5F-7843-8876-5A65C64E77C0',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'No UUID present',
|
|
31
|
+
output: 'Welcome to codex! Type your message and press enter.',
|
|
32
|
+
expected: null,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'Multiple UUIDs (should get first)',
|
|
36
|
+
output:
|
|
37
|
+
'Old: 1111e659-0e5f-7843-8876-5a65c64e77c0 New: 2222e659-0e5f-7843-8876-5a65c64e77c0',
|
|
38
|
+
expected: '1111e659-0e5f-7843-8876-5a65c64e77c0',
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
for (const testCase of testCases) {
|
|
43
|
+
console.log(`Testing: ${testCase.name}`);
|
|
44
|
+
const result = extractSessionId(testCase.output);
|
|
45
|
+
console.log(` Input: ${testCase.output}`);
|
|
46
|
+
console.log(` Expected: ${testCase.expected}`);
|
|
47
|
+
console.log(` Got: ${result}`);
|
|
48
|
+
expect(result).toBe(testCase.expected);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log('✅ All session extraction tests passed!\n');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should extract session ID from session metadata JSON', async () => {
|
|
55
|
+
console.log('\n=== Session Metadata Extraction Test ===');
|
|
56
|
+
|
|
57
|
+
const sessionMetaJson = `{"timestamp":"2025-10-15T05:30:20.265Z","type":"session_meta","payload":{"id":"0199e659-0e5f-7843-8876-5a65c64e77c0","timestamp":"2025-10-15T05:30:20.127Z","cwd":"/v1/code/project","originator":"codex_cli_rs"}}
|
|
58
|
+
{"timestamp":"2025-10-15T05:30:20.415Z","type":"response_item","payload":{"type":"message","role":"user"}}`;
|
|
59
|
+
|
|
60
|
+
const sessionId = extractSessionIdFromSessionMeta(sessionMetaJson);
|
|
61
|
+
console.log(`Extracted session ID: ${sessionId}`);
|
|
62
|
+
expect(sessionId).toBe('0199e659-0e5f-7843-8876-5a65c64e77c0');
|
|
63
|
+
|
|
64
|
+
console.log('✅ Session metadata extraction test passed!\n');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should demonstrate session tracking workflow', async () => {
|
|
68
|
+
console.log('\n=== Session Tracking Workflow Demo ===');
|
|
69
|
+
|
|
70
|
+
// Simulate codex output that would contain session information
|
|
71
|
+
const mockCodexOutputs = [
|
|
72
|
+
{
|
|
73
|
+
directory: 'logs/cwd1',
|
|
74
|
+
output:
|
|
75
|
+
'Starting new conversation... Session ID: aaaa1111-2222-3333-4444-bbbbccccdddd',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
directory: 'logs/cwd2',
|
|
79
|
+
output:
|
|
80
|
+
'Resuming session... Using ID: bbbb2222-3333-4444-5555-ccccddddeeee',
|
|
81
|
+
},
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
console.log('Simulating session capture from codex output:');
|
|
85
|
+
|
|
86
|
+
for (const mock of mockCodexOutputs) {
|
|
87
|
+
const sessionId = extractSessionId(mock.output);
|
|
88
|
+
console.log(`Directory: ${mock.directory}`);
|
|
89
|
+
console.log(`Output: ${mock.output}`);
|
|
90
|
+
console.log(`Captured Session ID: ${sessionId}`);
|
|
91
|
+
console.log('---');
|
|
92
|
+
|
|
93
|
+
expect(sessionId).toBeTruthy();
|
|
94
|
+
expect(sessionId).toMatch(
|
|
95
|
+
/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
console.log('✅ Workflow demonstration completed!\n');
|
|
100
|
+
});
|
|
101
|
+
});
|
package/ts/cli.spec.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { vi } from 'vitest';
|
|
2
|
-
|
|
3
|
-
// Mock cliYes function
|
|
4
|
-
const mockCliYes = vi.fn(async ({ prompt }) => {
|
|
5
|
-
return { exitCode: 0, logs: 'mocked logs' };
|
|
6
|
-
});
|
|
7
|
-
|
|
8
|
-
test('CLI args parsing', () => {
|
|
9
|
-
const rawArgs = ['claude', '--verbose', '--idle=5m', '--', 'write tests'];
|
|
10
|
-
const optionalIndex = (e: number) => (0 <= e ? e : undefined);
|
|
11
|
-
const dashIndex = optionalIndex(rawArgs.indexOf('--'));
|
|
12
|
-
const dashPrompt = dashIndex
|
|
13
|
-
? rawArgs.slice(dashIndex + 1).join(' ')
|
|
14
|
-
: undefined;
|
|
15
|
-
|
|
16
|
-
expect(dashPrompt).toBe('write tests');
|
|
17
|
-
expect(mockCliYes).toBeDefined();
|
|
18
|
-
});
|