claude-yes 1.15.0 → 1.16.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.
@@ -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 "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
- beforeAll(async () => {
12
- await execaCommand(`bun run build`).then(() =>
13
- console.log("Build successful"),
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
- const p = exec(
24
- `node dist/cli.js --exit-on-idle=3s "just write {on: 1} into ./.cache/flag.json"`,
25
- );
26
- const tr = new TransformStream<string, string>();
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
- const exit = async () =>
30
- await sflow(["\r", "/exit", "\r", "\r"])
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
- // ping function to exit claude when idle
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
- const { ping } = createIdleWatcher(() => exit(), 3000);
35
+ // ping function to exit claude when idle
40
36
 
41
- const output = await sflow(tr.readable)
42
- .by(fromStdio(p))
43
- .log()
44
- .forEach(() => ping())
45
- .text();
37
+ const { ping } = createIdleWatcher(() => exit(), 3000);
46
38
 
47
- // expect the file exists
48
- expect(existsSync(flagFile)).toBe(true);
49
- // expect the output contains the file path
50
- expect(output).toContain(flagFile);
39
+ const output = await sflow(tr.readable)
40
+ .by(fromStdio(p))
41
+ .log()
42
+ .forEach(() => ping())
43
+ .text();
51
44
 
52
- // expect the file content to be 'on'
53
- expect(await new Response(await readFile(flagFile)).json()).toEqual({
54
- on: 1,
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
- expect(p.exitCode).toBe(0); // expect the process to exit successfully
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
- // 30 seconds timeout for this test, it usually takes 13s to run (10s for claude to solve this problem, 3s for idle watcher to exit)
60
- }, 30e3);
55
+ expect(await pExitCode).toBe(0); // expect the process to exit successfully
56
+ expect(await readFile('./cli-rendered.log', 'utf8')).toBeTruthy();
61
57
 
62
- it.skip("CLI --exit-on-idle flag with default timeout", async () => {
63
- const p = exec(`node dist/cli.js "echo hello" --exit-on-idle`);
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("CLI --exit-on-idle flag with custom timeout", async () => {
72
- const p = exec(`node dist/cli.js --exit-on-idle=1s "echo hello"`);
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
@@ -6,7 +6,11 @@ import claudeYes from '.';
6
6
 
7
7
  // cli entry point
8
8
  const argv = yargs(hideBin(process.argv))
9
- .usage('Usage: $0 [options] [claude args]')
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
+ )
10
14
  .option('exit-on-idle', {
11
15
  type: 'string',
12
16
  default: '60s',
@@ -32,26 +36,12 @@ const argv = yargs(hideBin(process.argv))
32
36
  })
33
37
  .parseSync();
34
38
 
35
- const {
36
- exitOnIdle: exitOnIdleArg,
37
- continueOnCrash: continueOnCrashArg,
38
- logFile,
39
- ...rest
40
- } = argv;
41
-
42
- const claudeArgs = argv._.map((e) => String(e));
43
- const exitOnIdle = argv.exitOnIdle != null ? ms(argv.exitOnIdle) : undefined;
44
-
45
- argv.verbose &&
46
- console.debug('[claude-yes] Parsed args:', {
47
- exitOnIdle,
48
- continueOnCrash: continueOnCrashArg,
49
- claudeArgs,
50
- });
51
-
52
- await claudeYes({
53
- exitOnIdle,
54
- claudeArgs,
55
- continueOnCrash: continueOnCrashArg,
56
- logFile,
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,
57
45
  });
46
+
47
+ process.exit(exitCode ?? 1);
@@ -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
- export function createIdleWatcher(onIdle: () => void, idleTimeout: number): { ping: () => void; getLastActiveTime: () => Date; } {
3
- let lastActiveTime = new Date();
4
- let idleTimeoutId: NodeJS.Timeout | null = null;
5
- return {
6
- ping: () => {
7
- if (idleTimeoutId) {
8
- clearTimeout(idleTimeoutId);
9
- }
10
- idleTimeoutId = setTimeout(() => {
11
- clearTimeout(idleTimeoutId!);
12
- onIdle();
13
- }, idleTimeout);
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
  }