create-claude-workspace 1.1.151 → 2.0.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.
Files changed (56) hide show
  1. package/README.md +33 -1
  2. package/dist/index.js +29 -56
  3. package/dist/scheduler/agents/health-checker.mjs +98 -0
  4. package/dist/scheduler/agents/health-checker.spec.js +143 -0
  5. package/dist/scheduler/agents/orchestrator.mjs +149 -0
  6. package/dist/scheduler/agents/orchestrator.spec.js +87 -0
  7. package/dist/scheduler/agents/prompt-builder.mjs +204 -0
  8. package/dist/scheduler/agents/prompt-builder.spec.js +240 -0
  9. package/dist/scheduler/agents/worker-pool.mjs +137 -0
  10. package/dist/scheduler/agents/worker-pool.spec.js +45 -0
  11. package/dist/scheduler/git/ci-watcher.mjs +93 -0
  12. package/dist/scheduler/git/ci-watcher.spec.js +35 -0
  13. package/dist/scheduler/git/manager.mjs +228 -0
  14. package/dist/scheduler/git/manager.spec.js +198 -0
  15. package/dist/scheduler/git/release.mjs +117 -0
  16. package/dist/scheduler/git/release.spec.js +175 -0
  17. package/dist/scheduler/index.mjs +309 -0
  18. package/dist/scheduler/index.spec.js +72 -0
  19. package/dist/scheduler/integration.spec.js +289 -0
  20. package/dist/scheduler/loop.mjs +435 -0
  21. package/dist/scheduler/loop.spec.js +139 -0
  22. package/dist/scheduler/state/session.mjs +14 -0
  23. package/dist/scheduler/state/session.spec.js +36 -0
  24. package/dist/scheduler/state/state.mjs +102 -0
  25. package/dist/scheduler/state/state.spec.js +175 -0
  26. package/dist/scheduler/tasks/inbox.mjs +98 -0
  27. package/dist/scheduler/tasks/inbox.spec.js +168 -0
  28. package/dist/scheduler/tasks/parser.mjs +228 -0
  29. package/dist/scheduler/tasks/parser.spec.js +303 -0
  30. package/dist/scheduler/tasks/queue.mjs +152 -0
  31. package/dist/scheduler/tasks/queue.spec.js +223 -0
  32. package/dist/scheduler/types.mjs +20 -0
  33. package/dist/{scripts/lib → scheduler/ui}/tui.mjs +84 -41
  34. package/dist/{scripts/lib → scheduler/ui}/tui.spec.js +56 -0
  35. package/dist/scheduler/util/memory.mjs +126 -0
  36. package/dist/scheduler/util/memory.spec.js +165 -0
  37. package/dist/template/.claude/{profiles/angular.md → agents/angular-engineer.md} +9 -4
  38. package/dist/template/.claude/{profiles/react.md → agents/react-engineer.md} +9 -4
  39. package/dist/template/.claude/{profiles/svelte.md → agents/svelte-engineer.md} +9 -4
  40. package/dist/template/.claude/{profiles/vue.md → agents/vue-engineer.md} +9 -4
  41. package/package.json +3 -4
  42. package/dist/scripts/autonomous.mjs +0 -492
  43. package/dist/scripts/autonomous.spec.js +0 -46
  44. package/dist/scripts/docker-run.mjs +0 -462
  45. package/dist/scripts/integration.spec.js +0 -108
  46. package/dist/scripts/lib/formatter.mjs +0 -309
  47. package/dist/scripts/lib/formatter.spec.js +0 -262
  48. package/dist/scripts/lib/state.mjs +0 -44
  49. package/dist/scripts/lib/state.spec.js +0 -59
  50. package/dist/template/.claude/docker/.dockerignore +0 -8
  51. package/dist/template/.claude/docker/Dockerfile +0 -54
  52. package/dist/template/.claude/docker/docker-compose.yml +0 -22
  53. package/dist/template/.claude/docker/docker-entrypoint.sh +0 -101
  54. /package/dist/{scripts/lib/types.mjs → scheduler/shared-types.mjs} +0 -0
  55. /package/dist/{scripts/lib → scheduler/util}/idle-poll.mjs +0 -0
  56. /package/dist/{scripts/lib → scheduler/util}/idle-poll.spec.js +0 -0
@@ -1,492 +0,0 @@
1
- #!/usr/bin/env node
2
- // ─── Autonomous development loop ───
3
- // Headless runner for Claude Code orchestrator using Agent SDK.
4
- import { resolve } from 'node:path';
5
- import { existsSync, writeFileSync, unlinkSync, readFileSync, readdirSync } from 'node:fs';
6
- import { execSync } from 'node:child_process';
7
- import { DEFAULTS } from './lib/types.mjs';
8
- import { emptyCheckpoint, readCheckpoint, writeCheckpoint } from './lib/state.mjs';
9
- import { pollForNewWork } from './lib/idle-poll.mjs';
10
- import { TUI } from './lib/tui.mjs';
11
- import { query } from '@anthropic-ai/claude-agent-sdk';
12
- import { config as dotenvConfig } from '@dotenvx/dotenvx';
13
- // Disable SDK built-in agents (general-purpose, Explore, Plan, statusline-setup)
14
- process.env.CLAUDE_AGENT_SDK_DISABLE_BUILTIN_AGENTS = '1';
15
- // ─── Args ───
16
- function parseArgs(argv) {
17
- const opts = { ...DEFAULTS };
18
- const numFlag = (flag, i) => {
19
- const val = argv[i + 1];
20
- if (!val || val.startsWith('--')) {
21
- console.error(`${flag} requires a value`);
22
- process.exit(1);
23
- }
24
- const n = parseInt(val, 10);
25
- if (isNaN(n)) {
26
- console.error(`${flag} must be a number`);
27
- process.exit(1);
28
- }
29
- if (n < 0) {
30
- console.error(`${flag} must be non-negative`);
31
- process.exit(1);
32
- }
33
- return n;
34
- };
35
- const strFlag = (flag, i) => {
36
- const val = argv[i + 1];
37
- if (!val || val.startsWith('--')) {
38
- console.error(`${flag} requires a value`);
39
- process.exit(1);
40
- }
41
- return val;
42
- };
43
- for (let i = 0; i < argv.length; i++) {
44
- const arg = argv[i];
45
- if (arg === '--help' || arg === '-h') {
46
- opts.help = true;
47
- continue;
48
- }
49
- if (arg === '--skip-permissions') {
50
- opts.skipPermissions = true;
51
- continue;
52
- }
53
- if (arg === '--no-lock') {
54
- opts.noLock = true;
55
- continue;
56
- }
57
- if (arg === '--no-pull') {
58
- opts.noPull = true;
59
- continue;
60
- }
61
- if (arg === '--dry-run') {
62
- opts.dryRun = true;
63
- continue;
64
- }
65
- if (arg === '--resume') {
66
- opts.resume = true;
67
- continue;
68
- }
69
- if (arg === '--interactive' || arg === '-i') {
70
- opts.interactive = true;
71
- continue;
72
- }
73
- if (arg === '--max-iterations') {
74
- opts.maxIterations = numFlag(arg, i);
75
- i++;
76
- continue;
77
- }
78
- if (arg === '--max-turns') {
79
- opts.maxTurns = numFlag(arg, i);
80
- i++;
81
- continue;
82
- }
83
- if (arg === '--delay') {
84
- opts.delay = numFlag(arg, i);
85
- i++;
86
- continue;
87
- }
88
- if (arg === '--cooldown') {
89
- opts.cooldown = numFlag(arg, i);
90
- i++;
91
- continue;
92
- }
93
- if (arg === '--process-timeout') {
94
- opts.processTimeout = numFlag(arg, i);
95
- i++;
96
- continue;
97
- }
98
- if (arg === '--activity-timeout') {
99
- opts.activityTimeout = numFlag(arg, i);
100
- i++;
101
- continue;
102
- }
103
- if (arg === '--post-result-timeout') {
104
- opts.postResultTimeout = numFlag(arg, i);
105
- i++;
106
- continue;
107
- }
108
- if (arg === '--project-dir') {
109
- opts.projectDir = resolve(strFlag(arg, i));
110
- i++;
111
- continue;
112
- }
113
- if (arg === '--resume-session') {
114
- opts.resumeSession = strFlag(arg, i);
115
- i++;
116
- continue;
117
- }
118
- if (arg === '--notify-command') {
119
- opts.notifyCommand = strFlag(arg, i);
120
- i++;
121
- continue;
122
- }
123
- if (arg === '--log-file') {
124
- opts.logFile = strFlag(arg, i);
125
- i++;
126
- continue;
127
- }
128
- if (arg === '--idle-poll') {
129
- opts.idlePollInterval = numFlag(arg, i);
130
- i++;
131
- continue;
132
- }
133
- if (arg === '--max-idle') {
134
- opts.maxIdleTime = numFlag(arg, i);
135
- i++;
136
- continue;
137
- }
138
- if (arg.startsWith('--')) {
139
- console.error(`Unknown option: ${arg}`);
140
- process.exit(1);
141
- }
142
- }
143
- return opts;
144
- }
145
- function printHelp() {
146
- console.log(`
147
- Autonomous development loop for Claude Code.
148
-
149
- Usage: npx create-claude-workspace run [options]
150
-
151
- Options:
152
- --max-iterations <n> Max iterations (default: 50)
153
- --max-turns <n> Max turns per Claude invocation (default: 50)
154
- --delay <ms> Pause between tasks (default: 5000)
155
- --cooldown <ms> Wait after error (default: 60000)
156
- --process-timeout <ms> Max wall-clock time per invocation (default: 14400000 = 4h)
157
- --activity-timeout <ms> Max silence before kill (default: 900000 = 15min)
158
- --post-result-timeout <ms> Max wait after result (default: 30000 = 30s)
159
- --project-dir <path> Project directory (default: cwd)
160
- --skip-permissions Bypass all permission checks
161
- --resume Resume from checkpoint state
162
- --resume-session <id> Resume specific Claude session
163
- --notify-command <cmd> Shell command on critical events
164
- --log-file <path> Log file (default: .claude/autonomous.log)
165
- --no-lock Disable lock file
166
- --no-pull Skip auto git pull
167
- --idle-poll <ms> Poll interval when idle (default: 300000 = 5min)
168
- --max-idle <ms> Max idle time before exit, 0=unlimited (default: 0)
169
- --interactive, -i Interactive TUI with input prompt
170
- --dry-run Validate prerequisites only
171
- --help Show this message
172
- `);
173
- }
174
- // ─── Helpers ───
175
- function sleep(ms, ref) {
176
- return new Promise(r => {
177
- const t = setTimeout(r, ms);
178
- const check = setInterval(() => { if (ref.value) {
179
- clearTimeout(t);
180
- clearInterval(check);
181
- r();
182
- } }, 500);
183
- });
184
- }
185
- function formatDuration(ms) {
186
- if (ms < 1000)
187
- return `${ms}ms`;
188
- if (ms < 60_000)
189
- return `${(ms / 1000).toFixed(0)}s`;
190
- return `${(ms / 60_000).toFixed(1)}min`;
191
- }
192
- function notify(cmd, type, message, iteration) {
193
- if (!cmd)
194
- return;
195
- try {
196
- execSync(cmd, { env: { ...process.env, RALPH_EVENT: type, RALPH_MESSAGE: message, RALPH_ITERATION: String(iteration) }, timeout: 10_000, stdio: 'pipe' });
197
- }
198
- catch { /* */ }
199
- }
200
- function checkAuth() {
201
- if (process.env.ANTHROPIC_API_KEY)
202
- return true;
203
- try {
204
- const home = process.env.HOME || process.env.USERPROFILE || '';
205
- const creds = resolve(home, '.claude', '.credentials.json');
206
- if (existsSync(creds)) {
207
- const data = JSON.parse(readFileSync(creds, 'utf-8'));
208
- if (data.claudeAiOauth?.accessToken)
209
- return true;
210
- }
211
- }
212
- catch { /* */ }
213
- return false;
214
- }
215
- function acquireLock(dir, ui) {
216
- const lockFile = resolve(dir, '.claude/autonomous.lock');
217
- if (existsSync(lockFile)) {
218
- ui.warn('Lock file exists. Another instance running?');
219
- return false;
220
- }
221
- try {
222
- writeFileSync(lockFile, String(process.pid));
223
- return true;
224
- }
225
- catch {
226
- return false;
227
- }
228
- }
229
- function releaseLock(dir) {
230
- try {
231
- unlinkSync(resolve(dir, '.claude/autonomous.lock'));
232
- }
233
- catch { /* */ }
234
- }
235
- function readMemory(dir) {
236
- try {
237
- return readFileSync(resolve(dir, 'MEMORY.md'), 'utf-8');
238
- }
239
- catch {
240
- return '';
241
- }
242
- }
243
- function isProjectComplete(memory) {
244
- // Only trust the orchestrator's explicit completion marker in MEMORY.md.
245
- // Do NOT guess from TODO.md — the orchestrator handles phase transitions,
246
- // product-owner re-evaluations, and task additions internally.
247
- return /^Current Phase:.*PROJECT COMPLETE/mi.test(memory)
248
- || /^Project_Status:\s*complete/mi.test(memory);
249
- }
250
- function loadAgents(dir) {
251
- const agentsDir = resolve(dir, '.claude', 'agents');
252
- const agents = {};
253
- if (!existsSync(agentsDir))
254
- return agents;
255
- for (const file of readdirSync(agentsDir)) {
256
- if (!file.endsWith('.md'))
257
- continue;
258
- const name = file.replace('.md', '');
259
- const content = readFileSync(resolve(agentsDir, file), 'utf-8');
260
- // Parse YAML frontmatter
261
- let description = name;
262
- let model;
263
- const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
264
- if (fmMatch) {
265
- const fm = fmMatch[1];
266
- const descMatch = fm.match(/description:\s*["']?(.*?)["']?\s*$/m);
267
- if (descMatch)
268
- description = descMatch[1];
269
- const modelMatch = fm.match(/model:\s*(\S+)/m);
270
- if (modelMatch)
271
- model = modelMatch[1];
272
- }
273
- agents[name] = { description, prompt: content, model };
274
- }
275
- return agents;
276
- }
277
- function gitPull(dir) {
278
- try {
279
- execSync('git fetch --quiet && git pull --ff-only --quiet', { cwd: dir, timeout: 30_000, stdio: 'pipe' });
280
- return true;
281
- }
282
- catch {
283
- return false;
284
- }
285
- }
286
- // ─── Signal handling ───
287
- let stopping = false;
288
- const stoppingRef = { value: false };
289
- // ─── Main loop ───
290
- async function main() {
291
- const opts = parseArgs(process.argv.slice(2));
292
- if (opts.help) {
293
- printHelp();
294
- process.exit(0);
295
- }
296
- dotenvConfig({ path: resolve(opts.projectDir, '.env'), override: false, quiet: true });
297
- const logPath = resolve(opts.projectDir, opts.logFile);
298
- const tui = new TUI(logPath, opts.interactive);
299
- tui.setTopAgent('orchestrator');
300
- tui.banner();
301
- tui.info(`Project: ${opts.projectDir}`);
302
- tui.info(`Max iterations: ${opts.maxIterations} │ Max turns: ${opts.maxTurns}`);
303
- // Auth check
304
- if (!checkAuth()) {
305
- tui.error('Not authenticated. Either:');
306
- tui.error(' 1. Set ANTHROPIC_API_KEY env var (API billing)');
307
- tui.error(' 2. Run `claude login` to authenticate with Claude Max plan');
308
- tui.error(' For Docker: mount ~/.claude/.credentials.json into the container');
309
- process.exit(1);
310
- }
311
- tui.success('Authentication verified');
312
- // Lock
313
- if (!opts.noLock && !acquireLock(opts.projectDir, tui)) {
314
- process.exit(1);
315
- }
316
- // Git pull
317
- if (!opts.noPull) {
318
- if (gitPull(opts.projectDir))
319
- tui.info('Git: pulled latest changes');
320
- else
321
- tui.warn('Git: pull failed or not a git repo');
322
- }
323
- if (opts.dryRun) {
324
- tui.success('Dry run complete. All checks passed.');
325
- if (!opts.noLock)
326
- releaseLock(opts.projectDir);
327
- process.exit(0);
328
- }
329
- // Checkpoint
330
- let checkpoint;
331
- const existing = readCheckpoint(opts.projectDir);
332
- if (opts.resume && existing) {
333
- checkpoint = existing;
334
- tui.info(`Resuming from iteration ${checkpoint.iteration}`);
335
- }
336
- else {
337
- checkpoint = emptyCheckpoint();
338
- }
339
- // Signals
340
- const cleanup = () => {
341
- stopping = true;
342
- stoppingRef.value = true;
343
- if (!opts.noLock)
344
- releaseLock(opts.projectDir);
345
- writeCheckpoint(opts.projectDir, checkpoint);
346
- tui.warn('Interrupted. State saved.');
347
- process.exit(0);
348
- };
349
- process.on('SIGINT', cleanup);
350
- process.on('SIGTERM', cleanup);
351
- // Hotkey handler (interactive mode)
352
- tui.setHotkeyHandler((action) => {
353
- if (action === 'pause')
354
- tui.warn('Paused. Press Ctrl+Z to resume.');
355
- if (action === 'resume')
356
- tui.success('Resumed.');
357
- if (action === 'stop') {
358
- stopping = true;
359
- stoppingRef.value = true;
360
- tui.warn('Stopping after current iteration...');
361
- }
362
- if (action === 'quit')
363
- cleanup();
364
- });
365
- // ─── Loop ───
366
- for (let i = checkpoint.iteration + 1; i <= opts.maxIterations && !stopping; i++) {
367
- // Check completion → idle polling
368
- const memory = readMemory(opts.projectDir);
369
- if (isProjectComplete(memory)) {
370
- if (!checkpoint.completionVerified) {
371
- checkpoint.completionVerified = true;
372
- writeCheckpoint(opts.projectDir, checkpoint);
373
- }
374
- else {
375
- tui.info('Project complete. Entering idle polling...');
376
- let foundWork = false;
377
- const idleStart = Date.now();
378
- while (!stopping) {
379
- const poll = await pollForNewWork(opts.projectDir, { info: (m) => tui.info(m), warn: (m) => tui.warn(m), error: (m) => tui.error(m), debug: () => { } });
380
- if (poll.hasWork) {
381
- tui.success(`New work detected (${poll.source}). Resuming...`);
382
- checkpoint.completionVerified = false;
383
- writeCheckpoint(opts.projectDir, checkpoint);
384
- foundWork = true;
385
- break;
386
- }
387
- if (opts.maxIdleTime > 0 && Date.now() - idleStart >= opts.maxIdleTime) {
388
- tui.info('Max idle time reached. Exiting.');
389
- break;
390
- }
391
- await sleep(opts.idlePollInterval, stoppingRef);
392
- }
393
- if (!foundWork)
394
- break;
395
- continue;
396
- }
397
- }
398
- tui.setIteration(i, opts.maxIterations);
399
- tui.resetAgentStack();
400
- // Run Claude via Agent SDK
401
- try {
402
- const resumeOpts = {};
403
- if (opts.resumeSession) {
404
- resumeOpts.resume = opts.resumeSession;
405
- opts.resumeSession = null;
406
- }
407
- else if (opts.resume && checkpoint.lastSessionId) {
408
- resumeOpts.continue = true;
409
- opts.resume = false;
410
- }
411
- // Agents + CLAUDE.md loaded by SDK from .claude/ via settingSources: ['project']
412
- const queryOptions = {
413
- // Use settingSources to load agents from .claude/agents/ + CLAUDE.md
414
- settingSources: ['project'],
415
- agent: 'orchestrator',
416
- model: 'claude-opus-4-6',
417
- maxTurns: opts.maxTurns,
418
- cwd: opts.projectDir,
419
- };
420
- if (opts.skipPermissions) {
421
- queryOptions.permissionMode = 'bypassPermissions';
422
- queryOptions.allowDangerouslySkipPermissions = true;
423
- }
424
- if (resumeOpts.continue)
425
- queryOptions.continue = true;
426
- if (resumeOpts.resume)
427
- queryOptions.resume = resumeOpts.resume;
428
- const q = query({ prompt: 'Continue autonomous development. Pick the next task and execute the full workflow.', options: queryOptions });
429
- // Wire up interactive input → SDK message queue
430
- let sessionId = '';
431
- if (opts.interactive) {
432
- tui.setInputHandler((text) => {
433
- const msg = {
434
- type: 'user',
435
- message: { role: 'user', content: text },
436
- parent_tool_use_id: null,
437
- session_id: sessionId,
438
- priority: 'next',
439
- };
440
- async function* single() { yield msg; }
441
- q.streamInput(single()).catch(() => { });
442
- });
443
- }
444
- for await (const message of q) {
445
- tui.handleMessage(message);
446
- if (message.type === 'system' && message.subtype === 'init') {
447
- sessionId = message.session_id || '';
448
- }
449
- if (message.type === 'result') {
450
- checkpoint.lastSessionId = message.session_id ?? null;
451
- }
452
- }
453
- checkpoint.stats.iterations++;
454
- tui.iterationEnd();
455
- }
456
- catch (err) {
457
- tui.error(`${err.message}`);
458
- tui.warn(`Cooling down ${formatDuration(opts.cooldown)}...`);
459
- await sleep(opts.cooldown, stoppingRef);
460
- if (stopping)
461
- break;
462
- i--;
463
- continue;
464
- }
465
- checkpoint.iteration = i;
466
- writeCheckpoint(opts.projectDir, checkpoint);
467
- // Pause support
468
- while (tui.isPaused() && !stopping) {
469
- await sleep(1000, stoppingRef);
470
- }
471
- if (i < opts.maxIterations && !stopping) {
472
- tui.info(`Next iteration in ${formatDuration(opts.delay)}...`);
473
- await sleep(opts.delay, stoppingRef);
474
- }
475
- }
476
- // End
477
- if (!opts.noLock)
478
- releaseLock(opts.projectDir);
479
- writeCheckpoint(opts.projectDir, checkpoint);
480
- tui.success(`Loop ended after ${checkpoint.stats.iterations} iterations.`);
481
- tui.destroy();
482
- notify(opts.notifyCommand, 'completed', 'Loop ended', checkpoint.iteration);
483
- }
484
- // ─── Export for CLI integration ───
485
- export { main as runAutonomousLoop, parseArgs, printHelp };
486
- const isDirectRun = process.argv[1]?.replace(/\\/g, '/').endsWith('autonomous.mjs');
487
- if (isDirectRun) {
488
- main().catch(err => {
489
- console.error('Fatal:', err);
490
- process.exit(1);
491
- });
492
- }
@@ -1,46 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { parseArgs } from './autonomous.mjs';
3
- describe('parseArgs', () => {
4
- it('returns defaults for empty args', () => {
5
- const opts = parseArgs([]);
6
- expect(opts.maxIterations).toBe(50);
7
- expect(opts.maxTurns).toBe(50);
8
- expect(opts.skipPermissions).toBe(false);
9
- expect(opts.dryRun).toBe(false);
10
- expect(opts.resume).toBe(false);
11
- });
12
- it('parses boolean flags', () => {
13
- const opts = parseArgs(['--skip-permissions', '--dry-run', '--resume', '--no-lock', '--no-pull']);
14
- expect(opts.skipPermissions).toBe(true);
15
- expect(opts.dryRun).toBe(true);
16
- expect(opts.resume).toBe(true);
17
- expect(opts.noLock).toBe(true);
18
- expect(opts.noPull).toBe(true);
19
- });
20
- it('parses numeric flags', () => {
21
- const opts = parseArgs(['--max-iterations', '10', '--max-turns', '20', '--delay', '1000']);
22
- expect(opts.maxIterations).toBe(10);
23
- expect(opts.maxTurns).toBe(20);
24
- expect(opts.delay).toBe(1000);
25
- });
26
- it('parses string flags', () => {
27
- const opts = parseArgs(['--project-dir', '/tmp/proj', '--notify-command', 'echo hi', '--log-file', 'custom.log']);
28
- expect(opts.projectDir).toContain('proj');
29
- expect(opts.notifyCommand).toBe('echo hi');
30
- expect(opts.logFile).toBe('custom.log');
31
- });
32
- it('sets help flag', () => {
33
- expect(parseArgs(['--help']).help).toBe(true);
34
- expect(parseArgs(['-h']).help).toBe(true);
35
- });
36
- it('parses idle poll flags', () => {
37
- const opts = parseArgs(['--idle-poll', '60000', '--max-idle', '3600000']);
38
- expect(opts.idlePollInterval).toBe(60000);
39
- expect(opts.maxIdleTime).toBe(3600000);
40
- });
41
- it('uses defaults for idle poll flags when not provided', () => {
42
- const opts = parseArgs([]);
43
- expect(opts.idlePollInterval).toBe(5 * 60_000);
44
- expect(opts.maxIdleTime).toBe(0);
45
- });
46
- });