claude-yes 1.14.2 → 1.15.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.
- package/cli-idle.spec.ts +17 -0
- package/cli.test.ts +50 -67
- package/cli.ts +41 -41
- package/createIdleWatcher.ts +18 -16
- package/dist/cli.js +5380 -264
- package/dist/index.js +41 -21
- package/index.ts +60 -38
- package/package.json +4 -4
package/cli-idle.spec.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { exec } from 'child_process';
|
|
2
|
+
import { fromStdio } from 'from-node-stream';
|
|
3
|
+
import sflow from 'sflow';
|
|
4
|
+
import { sleepms } from './utils';
|
|
5
|
+
|
|
6
|
+
// 2025-08-11 ok
|
|
7
|
+
it.skip('CLI --exit-on-idle flag with custom timeout', async () => {
|
|
8
|
+
const p = exec(
|
|
9
|
+
`bunx tsx ./cli.ts --verbose --logFile=./cli-idle.log --exit-on-idle=3s "say hello and wait"`
|
|
10
|
+
);
|
|
11
|
+
const tr = new TransformStream<string, string>();
|
|
12
|
+
const output = await sflow(tr.readable).by(fromStdio(p)).log().text();
|
|
13
|
+
console.log(output);
|
|
14
|
+
expect(output).toContain('hello');
|
|
15
|
+
await sleepms(1000); // wait for process exit
|
|
16
|
+
expect(p.exitCode).toBe(0);
|
|
17
|
+
}, 20e3);
|
package/cli.test.ts
CHANGED
|
@@ -1,79 +1,62 @@
|
|
|
1
|
-
import { execaCommand } from
|
|
2
|
-
import { fromStdio } from
|
|
3
|
-
import { exec } from
|
|
4
|
-
import { existsSync } from
|
|
5
|
-
import { readFile, unlink } from
|
|
6
|
-
import sflow from
|
|
7
|
-
import { beforeAll, describe, expect, it } from
|
|
8
|
-
import { createIdleWatcher } from
|
|
9
|
-
import { sleepms } from
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
)
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
describe("CLI Tests", () => {
|
|
18
|
-
it("Write file with auto bypass permission prompt", async () => {
|
|
19
|
-
const flagFile = "./.cache/flag.json";
|
|
20
|
-
// clean
|
|
1
|
+
import { execaCommand } from 'execa';
|
|
2
|
+
import { fromStdio } from 'from-node-stream';
|
|
3
|
+
import { exec } from 'node:child_process';
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
5
|
+
import { readFile, unlink } from 'node:fs/promises';
|
|
6
|
+
import sflow from 'sflow';
|
|
7
|
+
import { beforeAll, describe, expect, it } from 'vitest';
|
|
8
|
+
import { createIdleWatcher } from './createIdleWatcher';
|
|
9
|
+
import { sleepms } from './utils';
|
|
10
|
+
|
|
11
|
+
it('Write file with auto bypass prompts', async () => {
|
|
12
|
+
const flagFile = './.cache/flag.json';
|
|
13
|
+
await cleanup();
|
|
14
|
+
async function cleanup() {
|
|
21
15
|
await unlink(flagFile).catch(() => {});
|
|
16
|
+
await unlink('./cli-rendered.log').catch(() => {});
|
|
17
|
+
}
|
|
22
18
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const w = tr.writable.getWriter();
|
|
19
|
+
const p = exec(
|
|
20
|
+
`bunx tsx ./cli.ts --logFile=./cli-rendered.log --exit-on-idle=3s "just write {on: 1} into ./.cache/flag.json and wait"`
|
|
21
|
+
);
|
|
22
|
+
const pExitCode = new Promise<number | null>((r) => p.once('exit', r));
|
|
28
23
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
.forEach(async (e) => {
|
|
32
|
-
await sleepms(200);
|
|
33
|
-
await w.write(e);
|
|
34
|
-
})
|
|
35
|
-
.run();
|
|
24
|
+
const tr = new TransformStream<string, string>();
|
|
25
|
+
const w = tr.writable.getWriter();
|
|
36
26
|
|
|
37
|
-
|
|
27
|
+
const exit = async () =>
|
|
28
|
+
await sflow(['\r', '/exit', '\r', '\r'])
|
|
29
|
+
.forEach(async (e) => {
|
|
30
|
+
await sleepms(200);
|
|
31
|
+
await w.write(e);
|
|
32
|
+
})
|
|
33
|
+
.run();
|
|
38
34
|
|
|
39
|
-
|
|
35
|
+
// ping function to exit claude when idle
|
|
40
36
|
|
|
41
|
-
|
|
42
|
-
.by(fromStdio(p))
|
|
43
|
-
.log()
|
|
44
|
-
.forEach(() => ping())
|
|
45
|
-
.text();
|
|
37
|
+
const { ping } = createIdleWatcher(() => exit(), 3000);
|
|
46
38
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
39
|
+
const output = await sflow(tr.readable)
|
|
40
|
+
.by(fromStdio(p))
|
|
41
|
+
.log()
|
|
42
|
+
.forEach(() => ping())
|
|
43
|
+
.text();
|
|
51
44
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
45
|
+
// expect the file exists
|
|
46
|
+
expect(existsSync(flagFile)).toBe(true);
|
|
47
|
+
// expect the output contains the file path
|
|
48
|
+
expect(output).toContain(flagFile);
|
|
56
49
|
|
|
57
|
-
|
|
50
|
+
// expect the file content to be 'on'
|
|
51
|
+
expect(await new Response(await readFile(flagFile)).json()).toEqual({
|
|
52
|
+
on: 1,
|
|
53
|
+
});
|
|
58
54
|
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
expect(await pExitCode).toBe(0); // expect the process to exit successfully
|
|
56
|
+
expect(await readFile('./cli-rendered.log', 'utf8')).toBeTruthy();
|
|
61
57
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const tr = new TransformStream<string, string>();
|
|
65
|
-
const output = await sflow(tr.readable).by(fromStdio(p)).log().text();
|
|
66
|
-
expect(output).toContain("hello");
|
|
67
|
-
await sleepms(1000); // wait for process exit
|
|
68
|
-
expect(p.exitCode).toBe(0);
|
|
69
|
-
}, 30e3);
|
|
58
|
+
// clean
|
|
59
|
+
await cleanup();
|
|
70
60
|
|
|
71
|
-
it(
|
|
72
|
-
|
|
73
|
-
const tr = new TransformStream<string, string>();
|
|
74
|
-
const output = await sflow(tr.readable).by(fromStdio(p)).log().text();
|
|
75
|
-
expect(output).toContain("hello");
|
|
76
|
-
await sleepms(1000); // wait for process exit
|
|
77
|
-
expect(p.exitCode).toBe(0);
|
|
78
|
-
}, 30e3);
|
|
79
|
-
});
|
|
61
|
+
// it usually takes 13s to run (10s for claude to solve this problem, 3s for idle watcher to exit)
|
|
62
|
+
}, 30e3);
|
package/cli.ts
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import ms from
|
|
3
|
-
import
|
|
4
|
-
import
|
|
2
|
+
import ms from 'enhanced-ms';
|
|
3
|
+
import yargs from 'yargs';
|
|
4
|
+
import { hideBin } from 'yargs/helpers';
|
|
5
|
+
import claudeYes from '.';
|
|
5
6
|
|
|
6
7
|
// cli entry point
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
const argv = yargs(hideBin(process.argv))
|
|
9
|
+
.usage('Usage: $0 [options] [--] [claude args]')
|
|
10
|
+
.example(
|
|
11
|
+
'$0 --exit-on-idle=30s --continue-on-crash "help me solve all todos in my codebase"',
|
|
12
|
+
'Run Claude with a 30 seconds idle timeout and continue on crash'
|
|
13
|
+
)
|
|
14
|
+
.option('exit-on-idle', {
|
|
15
|
+
type: 'string',
|
|
16
|
+
default: '60s',
|
|
17
|
+
description: 'Exit after being idle for specified duration',
|
|
18
|
+
})
|
|
19
|
+
.option('continue-on-crash', {
|
|
20
|
+
type: 'boolean',
|
|
21
|
+
default: true,
|
|
22
|
+
description: 'Continue running even if Claude crashes',
|
|
23
|
+
})
|
|
24
|
+
.option('log-file', {
|
|
25
|
+
type: 'string',
|
|
26
|
+
description: 'Path to log file for output logging',
|
|
27
|
+
})
|
|
28
|
+
.option('verbose', {
|
|
29
|
+
type: 'boolean',
|
|
30
|
+
default: false,
|
|
31
|
+
description: 'Enable verbose logging',
|
|
32
|
+
})
|
|
33
|
+
.parserConfiguration({
|
|
34
|
+
'unknown-options-as-args': true,
|
|
35
|
+
'halt-at-non-option': true,
|
|
36
|
+
})
|
|
37
|
+
.parseSync();
|
|
16
38
|
|
|
17
|
-
const {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
39
|
+
const { exitCode, logs } = await claudeYes({
|
|
40
|
+
exitOnIdle: argv.exitOnIdle != null ? ms(argv.exitOnIdle) : undefined,
|
|
41
|
+
claudeArgs: argv._.map((e) => String(e)),
|
|
42
|
+
continueOnCrash: argv.continueOnCrash,
|
|
43
|
+
logFile: argv.logFile,
|
|
44
|
+
verbose: argv.verbose,
|
|
22
45
|
});
|
|
23
46
|
|
|
24
|
-
|
|
25
|
-
let exitOnIdle: boolean | number | undefined;
|
|
26
|
-
if (typeof exitOnIdleArg === 'string') {
|
|
27
|
-
if (exitOnIdleArg === '') {
|
|
28
|
-
exitOnIdle = true; // default timeout will be used
|
|
29
|
-
} else {
|
|
30
|
-
exitOnIdle = ms(exitOnIdleArg); // parse duration string like "5s", "30s", "1m"
|
|
31
|
-
}
|
|
32
|
-
} else {
|
|
33
|
-
exitOnIdle = undefined;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
// console.debug('Parsed args:', {
|
|
38
|
-
// exitOnIdle,
|
|
39
|
-
// continueOnCrash: continueOnCrashArg,
|
|
40
|
-
// claudeArgs,
|
|
41
|
-
// });
|
|
42
|
-
|
|
43
|
-
await claudeYes({
|
|
44
|
-
exitOnIdle,
|
|
45
|
-
claudeArgs,
|
|
46
|
-
continueOnCrash: continueOnCrashArg,
|
|
47
|
-
});
|
|
47
|
+
process.exit(exitCode ?? 1);
|
package/createIdleWatcher.ts
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
|
+
export function createIdleWatcher(
|
|
2
|
+
onIdle: () => void,
|
|
3
|
+
idleTimeout: number
|
|
4
|
+
): { ping: () => void; getLastActiveTime: () => Date } {
|
|
5
|
+
let lastActiveTime = new Date();
|
|
6
|
+
let idleTimeoutId: NodeJS.Timeout | null = null;
|
|
1
7
|
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
lastActiveTime = new Date();
|
|
15
|
-
},
|
|
16
|
-
getLastActiveTime: () => lastActiveTime
|
|
17
|
-
};
|
|
8
|
+
return {
|
|
9
|
+
ping: () => {
|
|
10
|
+
if (idleTimeoutId) clearTimeout(idleTimeoutId);
|
|
11
|
+
|
|
12
|
+
lastActiveTime = new Date();
|
|
13
|
+
idleTimeoutId = setTimeout(() => {
|
|
14
|
+
clearTimeout(idleTimeoutId!);
|
|
15
|
+
onIdle();
|
|
16
|
+
}, idleTimeout);
|
|
17
|
+
},
|
|
18
|
+
getLastActiveTime: () => lastActiveTime,
|
|
19
|
+
};
|
|
18
20
|
}
|