create-claude-workspace 2.3.31 → 2.3.33

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 CHANGED
@@ -28,9 +28,11 @@ Answer the discovery questions (non-technical). Claude generates the full pipeli
28
28
  ### Start Autonomous Development
29
29
 
30
30
  ```bash
31
- npx create-claude-workspace scheduler # start (1 agent)
32
- npx create-claude-workspace scheduler --concurrency 3 # 3 parallel agents
33
- npx create-claude-workspace scheduler --resume # resume from saved state
31
+ npx create-claude-workspace run # start (1 agent)
32
+ npx create-claude-workspace run --concurrency 3 # 3 parallel agents
33
+ npx create-claude-workspace run -i # interactive mode (user input only)
34
+ npx create-claude-workspace run --task-mode local # force local task mode
35
+ npx create-claude-workspace run --resume # resume from saved state
34
36
  ```
35
37
 
36
38
  Or interactively: `claude --agent orchestrator`, then `/ralph-loop:ralph-loop Continue autonomous development according to CLAUDE.md`
@@ -45,12 +47,27 @@ The scheduler replaces the single-orchestrator model with parallel agent executi
45
47
  - **N agents in parallel** — independent tasks run concurrently in separate git worktrees
46
48
 
47
49
  ```bash
48
- npx create-claude-workspace scheduler # start (1 agent)
49
- npx create-claude-workspace scheduler --concurrency 3 # 3 parallel agents
50
- npx create-claude-workspace scheduler --resume # resume from saved state
51
- npx create-claude-workspace scheduler --help # all options
50
+ npx create-claude-workspace run # start (1 agent)
51
+ npx create-claude-workspace run --concurrency 3 # 3 parallel agents
52
+ npx create-claude-workspace run -i # interactive mode
53
+ npx create-claude-workspace run --task-mode local # force local mode
54
+ npx create-claude-workspace run --help # all options
52
55
  ```
53
56
 
57
+ #### Task modes
58
+
59
+ The scheduler supports three task modes (`--task-mode`):
60
+
61
+ | Mode | Source | Use case |
62
+ |------|--------|----------|
63
+ | **local** | `TODO.md` / `tasks.json` | Solo development, no issue tracker |
64
+ | **platform** | GitHub/GitLab issues | Team workflows with issue tracking |
65
+ | **interactive** | User TUI input only | Ad-hoc tasks, exploration, prototyping |
66
+
67
+ Auto-detected at startup: if the repo has a remote with `status::` labeled issues → `platform`, otherwise → `local`. Override with `--task-mode` flag or set `Task Platform` in CLAUDE.md via `project-initializer`.
68
+
69
+ The `--interactive` (`-i`) flag implies `--task-mode interactive`. Type task descriptions directly in the TUI — each becomes a task that goes through the full pipeline (plan → implement → test → review → commit). Pending tasks are shown in the status bar queue.
70
+
54
71
  #### Communicate at runtime
55
72
 
56
73
  Write to `.claude/scheduler/inbox.json` while the scheduler is running:
package/dist/index.js CHANGED
@@ -70,6 +70,8 @@ ${C.b}Scheduler options:${C.n}
70
70
  --concurrency <n> Parallel agents (default: 1)
71
71
  --max-iterations <n> Max iterations (default: 50)
72
72
  --max-turns <n> Max turns per agent invocation (default: 50)
73
+ --task-mode <mode> Task source: local, platform, or interactive
74
+ --interactive, -i Interactive mode — tasks from user input only
73
75
  --resume Resume from saved state
74
76
  --dry-run Validate prerequisites only
75
77
  See 'npx create-claude-workspace run --help' for all options.
@@ -78,6 +80,8 @@ ${C.b}Examples:${C.n}
78
80
  npx create-claude-workspace # scaffold in current directory
79
81
  npx create-claude-workspace run # start scheduler
80
82
  npx create-claude-workspace run --concurrency 3 # 3 parallel agents
83
+ npx create-claude-workspace run -i # interactive mode (user input only)
84
+ npx create-claude-workspace run --task-mode local # force local task mode
81
85
  npx create-claude-workspace run --resume # resume from state
82
86
  npx create-claude-workspace validate # check prerequisites
83
87
 
@@ -244,6 +248,9 @@ async function main() {
244
248
  console.log(` ${C.d}# Multi-agent scheduler${C.n}`);
245
249
  console.log(` npx create-claude-workspace run`);
246
250
  console.log('');
251
+ console.log(` ${C.d}# Interactive mode (tasks from user input)${C.n}`);
252
+ console.log(` npx create-claude-workspace run -i`);
253
+ console.log('');
247
254
  console.log(` ${C.d}# With parallel agents${C.n}`);
248
255
  console.log(` npx create-claude-workspace run --concurrency 3`);
249
256
  console.log('');
@@ -2,7 +2,7 @@
2
2
  // ─── Multi-agent scheduler entry point ───
3
3
  // Replaces the single-orchestrator autonomous loop with parallel agent execution.
4
4
  import { resolve } from 'node:path';
5
- import { existsSync, mkdirSync } from 'node:fs';
5
+ import { existsSync, mkdirSync, readFileSync } from 'node:fs';
6
6
  import { config as dotenvConfig } from '@dotenvx/dotenvx';
7
7
  import { SCHEDULER_DEFAULTS } from './types.mjs';
8
8
  import { detectCIPlatform, checkPlatformCLI, installPlatformCLI } from './git/ci-watcher.mjs';
@@ -15,6 +15,7 @@ import { checkAuth } from './agents/health-checker.mjs';
15
15
  import { pollForNewWork } from './util/idle-poll.mjs';
16
16
  import { TUI } from './ui/tui.mjs';
17
17
  import { DiscoveredIssueStore } from './tools/report-issue.mjs';
18
+ import { readInbox, writeToInbox } from './tasks/inbox.mjs';
18
19
  // ─── Args ───
19
20
  export function parseSchedulerArgs(argv) {
20
21
  const opts = { ...SCHEDULER_DEFAULTS };
@@ -131,8 +132,8 @@ export function parseSchedulerArgs(argv) {
131
132
  }
132
133
  if (arg === '--task-mode') {
133
134
  const val = strFlag(arg, i);
134
- if (val !== 'local' && val !== 'platform') {
135
- console.error(`--task-mode must be 'local' or 'platform'`);
135
+ if (val !== 'local' && val !== 'platform' && val !== 'interactive') {
136
+ console.error(`--task-mode must be 'local', 'platform', or 'interactive'`);
136
137
  process.exit(1);
137
138
  }
138
139
  mutable.taskMode = val;
@@ -146,10 +147,32 @@ export function parseSchedulerArgs(argv) {
146
147
  }
147
148
  return opts;
148
149
  }
150
+ function readTaskPlatformFromClaudeMd(projectDir) {
151
+ for (const p of ['CLAUDE.md', '.claude/CLAUDE.md']) {
152
+ const fp = resolve(projectDir, p);
153
+ if (!existsSync(fp))
154
+ continue;
155
+ try {
156
+ const content = readFileSync(fp, 'utf-8');
157
+ const match = content.match(/^\s*-\s*\*\*Task Platform\*\*:\s*(\S+)/m);
158
+ if (match)
159
+ return match[1].toLowerCase();
160
+ }
161
+ catch { /* ignore */ }
162
+ }
163
+ return null;
164
+ }
149
165
  function detectTaskMode(projectDir) {
166
+ // Respect explicit Task Platform setting from CLAUDE.md
167
+ const claudeMdPlatform = readTaskPlatformFromClaudeMd(projectDir);
168
+ if (claudeMdPlatform === 'local')
169
+ return 'local';
150
170
  const platform = detectCIPlatform(projectDir);
151
171
  if (platform === 'none')
152
172
  return 'local';
173
+ // CLAUDE.md says github/gitlab → platform mode
174
+ if (claudeMdPlatform === 'github' || claudeMdPlatform === 'gitlab')
175
+ return 'platform';
153
176
  try {
154
177
  const issues = fetchOpenIssues(projectDir, platform);
155
178
  if (issues.length === 0)
@@ -190,7 +213,8 @@ Options:
190
213
  --no-pull Skip auto git pull
191
214
  --idle-poll <ms> Poll interval when idle (default: 300000)
192
215
  --max-idle <ms> Max idle time before exit (default: 0 = unlimited)
193
- --interactive, -i Interactive TUI with input prompt
216
+ --task-mode <mode> Task source: local, platform, or interactive
217
+ --interactive, -i Interactive mode — tasks from user input only (implies --task-mode interactive)
194
218
  --dry-run Validate prerequisites only
195
219
  --help Show this message
196
220
  `);
@@ -225,6 +249,13 @@ export async function runScheduler(opts) {
225
249
  const logPath = resolve(opts.projectDir, opts.logFile);
226
250
  const tui = new TUI(logPath, opts.interactive);
227
251
  tui.setTopAgent('scheduler');
252
+ // Wire TUI input → inbox (interactive mode: user text becomes tasks)
253
+ if (opts.interactive) {
254
+ tui.setInputHandler((text) => {
255
+ writeToInbox(opts.projectDir, [{ type: 'add-task', title: text }]);
256
+ tui.addPendingInput(text); // tracks in status bar queue + logs "Queued: text"
257
+ });
258
+ }
228
259
  const logger = {
229
260
  info: (m) => tui.info(m),
230
261
  warn: (m) => tui.warn(m),
@@ -304,7 +335,12 @@ export async function runScheduler(opts) {
304
335
  }
305
336
  // Task mode detection — always re-detect unless user explicitly set --task-mode
306
337
  // (stored state may be stale if CLI was installed/authenticated since last run)
307
- if (opts.taskMode) {
338
+ if (opts.interactive && !opts.taskMode) {
339
+ // Interactive flag implies interactive task mode (no issues, no TODO)
340
+ state.taskMode = 'interactive';
341
+ logger.info('Task mode: interactive (user input only)');
342
+ }
343
+ else if (opts.taskMode) {
308
344
  state.taskMode = opts.taskMode;
309
345
  logger.info(`Task mode (from flag): ${state.taskMode}`);
310
346
  }
@@ -369,6 +405,15 @@ export async function runScheduler(opts) {
369
405
  };
370
406
  process.on('SIGINT', cleanup);
371
407
  process.on('SIGTERM', cleanup);
408
+ // Wire TUI hotkeys → scheduler control
409
+ if (opts.interactive) {
410
+ tui.setHotkeyHandler((action) => {
411
+ if (action === 'stop' || action === 'quit') {
412
+ stopping = true;
413
+ stoppingRef.value = true;
414
+ }
415
+ });
416
+ }
372
417
  // ─── Loop ───
373
418
  for (let i = state.iteration; i < opts.maxIterations && !stopping; i++) {
374
419
  tui.setIteration(i + 1, opts.maxIterations);
@@ -384,23 +429,36 @@ export async function runScheduler(opts) {
384
429
  onMessage,
385
430
  onSpawnStart,
386
431
  onSpawnEnd,
432
+ onInboxTaskPicked: opts.interactive ? () => tui.consumePendingInput() : undefined,
387
433
  });
388
434
  if (!workDone) {
389
- // No work idle polling
390
- logger.info('No work available. Entering idle polling...');
391
- const idleStart = Date.now();
392
- while (!stopping) {
393
- const poll = await pollForNewWork(opts.projectDir, logger, state.taskMode, state.completedTasks);
394
- if (poll.hasWork) {
395
- logger.info(`New work detected (${poll.source}). Resuming...`);
396
- break;
435
+ if (state.taskMode === 'interactive') {
436
+ // Interactive mode: wait for user input (no external polling)
437
+ logger.info('Waiting for input...');
438
+ while (!stopping) {
439
+ const pending = readInbox(opts.projectDir);
440
+ if (pending.length > 0 || discoveredIssueStore.peek().length > 0)
441
+ break;
442
+ await sleep(500, stoppingRef);
397
443
  }
398
- if (opts.maxIdleTime > 0 && Date.now() - idleStart >= opts.maxIdleTime) {
399
- logger.info('Max idle time reached. Exiting.');
400
- stopping = true;
401
- break;
444
+ }
445
+ else {
446
+ // No work → idle polling (platform/local)
447
+ logger.info('No work available. Entering idle polling...');
448
+ const idleStart = Date.now();
449
+ while (!stopping) {
450
+ const poll = await pollForNewWork(opts.projectDir, logger, state.taskMode, state.completedTasks);
451
+ if (poll.hasWork) {
452
+ logger.info(`New work detected (${poll.source}). Resuming...`);
453
+ break;
454
+ }
455
+ if (opts.maxIdleTime > 0 && Date.now() - idleStart >= opts.maxIdleTime) {
456
+ logger.info('Max idle time reached. Exiting.');
457
+ stopping = true;
458
+ break;
459
+ }
460
+ await sleep(opts.idlePollInterval, stoppingRef);
402
461
  }
403
- await sleep(opts.idlePollInterval, stoppingRef);
404
462
  }
405
463
  continue;
406
464
  }
@@ -40,6 +40,7 @@ export async function runIteration(deps) {
40
40
  const newTask = addTaskMessageToTask(addMsg, state.currentPhase, nextId);
41
41
  inboxTasks.push(newTask);
42
42
  logger.info(`[inbox] New task: ${newTask.title}`);
43
+ deps.onInboxTaskPicked?.(newTask.title);
43
44
  appendEvent(projectDir, createEvent('task_started', { taskId: nextId, detail: `inbox: ${newTask.title}` }));
44
45
  }
45
46
  else if (msg.type === 'message') {
@@ -79,9 +80,12 @@ export async function runIteration(deps) {
79
80
  state._recoveryDone = true;
80
81
  await recoverOrphanedWorktrees(projectDir, state, logger, deps);
81
82
  }
82
- // Load tasks (dual mode: platform issues or local TODO.md)
83
+ // Load tasks (triple mode: interactive = inbox only, platform = issues, local = TODO.md)
83
84
  let tasks;
84
- if (state.taskMode === 'platform') {
85
+ if (state.taskMode === 'interactive') {
86
+ tasks = []; // No external sources — only inbox + discovered
87
+ }
88
+ else if (state.taskMode === 'platform') {
85
89
  const platform = detectCIPlatform(projectDir);
86
90
  if (platform === 'none') {
87
91
  logger.error('Platform mode requires a git remote (github/gitlab)');
@@ -171,6 +171,10 @@ function readTodoSafe(projectDir) {
171
171
  }
172
172
  // ─── Main poll function ───
173
173
  export async function pollForNewWork(projectDir, log, taskMode = 'local', completedTasks = []) {
174
+ // Interactive mode: no external sources to poll — caller handles inbox directly
175
+ if (taskMode === 'interactive') {
176
+ return { hasWork: false, source: null, issueCount: 0 };
177
+ }
174
178
  // Platform mode: check issues directly via CLI
175
179
  if (taskMode === 'platform') {
176
180
  return pollPlatformIssues(projectDir, log, completedTasks);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-claude-workspace",
3
- "version": "2.3.31",
3
+ "version": "2.3.33",
4
4
  "author": "",
5
5
  "repository": {
6
6
  "type": "git",