create-claude-workspace 2.3.32 → 2.3.34

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';
@@ -147,10 +147,32 @@ export function parseSchedulerArgs(argv) {
147
147
  }
148
148
  return opts;
149
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
+ }
150
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';
151
170
  const platform = detectCIPlatform(projectDir);
152
171
  if (platform === 'none')
153
172
  return 'local';
173
+ // CLAUDE.md says github/gitlab → platform mode
174
+ if (claudeMdPlatform === 'github' || claudeMdPlatform === 'gitlab')
175
+ return 'platform';
154
176
  try {
155
177
  const issues = fetchOpenIssues(projectDir, platform);
156
178
  if (issues.length === 0)
@@ -231,7 +253,7 @@ export async function runScheduler(opts) {
231
253
  if (opts.interactive) {
232
254
  tui.setInputHandler((text) => {
233
255
  writeToInbox(opts.projectDir, [{ type: 'add-task', title: text }]);
234
- tui.info(`Task queued: ${text}`);
256
+ tui.addPendingInput(text); // tracks in status bar queue + logs "Queued: text"
235
257
  });
236
258
  }
237
259
  const logger = {
@@ -407,17 +429,19 @@ export async function runScheduler(opts) {
407
429
  onMessage,
408
430
  onSpawnStart,
409
431
  onSpawnEnd,
432
+ onInboxTaskPicked: opts.interactive ? () => tui.consumePendingInput() : undefined,
410
433
  });
411
434
  if (!workDone) {
412
435
  if (state.taskMode === 'interactive') {
413
436
  // Interactive mode: wait for user input (no external polling)
414
- logger.info('Waiting for input...');
437
+ tui.setIdle(true);
415
438
  while (!stopping) {
416
439
  const pending = readInbox(opts.projectDir);
417
440
  if (pending.length > 0 || discoveredIssueStore.peek().length > 0)
418
441
  break;
419
442
  await sleep(500, stoppingRef);
420
443
  }
444
+ tui.setIdle(false);
421
445
  }
422
446
  else {
423
447
  // No work → idle polling (platform/local)
@@ -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') {
@@ -52,7 +52,7 @@ export class TUI {
52
52
  this.state = {
53
53
  iteration: 0, maxIter: 0, loopStart: Date.now(), iterStart: 0,
54
54
  tools: 0, tokensIn: 0, tokensOut: 0, agents: [], taskName: '',
55
- tasksDone: 0, tasksTotal: 0, paused: false, inputBuf: '',
55
+ tasksDone: 0, tasksTotal: 0, paused: false, idle: false, inputBuf: '',
56
56
  pendingInputs: [],
57
57
  };
58
58
  if (this.interactive) {
@@ -157,13 +157,13 @@ export class TUI {
157
157
  isPaused() { return this.state.paused; }
158
158
  addPendingInput(text) {
159
159
  this.state.pendingInputs.push(text);
160
- this.log(` ${ANSI_COLORS.gray}${ts()}${RESET} ${ANSI_COLORS.magenta}▲${RESET} ${ANSI_COLORS.white}Queued:${RESET} ${text}`);
160
+ this.log(` ${ANSI_COLORS.gray}${ts()}${RESET} ${ANSI_COLORS.magenta}${BOLD}▶ USER${RESET} ${ANSI_COLORS.white}${text}${RESET} ${ANSI_COLORS.gray}[queued #${this.state.pendingInputs.length}]${RESET}`, ` ${ts()} ▶ USER ${text} [queued #${this.state.pendingInputs.length}]`);
161
161
  this.renderStatusBar();
162
162
  }
163
163
  consumePendingInput() {
164
164
  const item = this.state.pendingInputs.shift();
165
165
  if (item) {
166
- this.log(` ${ANSI_COLORS.gray}${ts()}${RESET} ${ANSI_COLORS.green}▼${RESET} ${ANSI_COLORS.white}Processing:${RESET} ${item}`);
166
+ this.log(` ${ANSI_COLORS.gray}${ts()}${RESET} ${ANSI_COLORS.green}${BOLD}▶ START${RESET} ${ANSI_COLORS.white}${item}${RESET}`, ` ${ts()} ▶ START ${item}`);
167
167
  this.renderStatusBar();
168
168
  }
169
169
  return item;
@@ -194,24 +194,36 @@ export class TUI {
194
194
  lines.push(` ${ANSI_COLORS.gray}› type to send input${RESET}`);
195
195
  }
196
196
  // Line 3: Status bar (stats)
197
- const elapsed = fmtDur(Date.now() - s.loopStart);
198
- const iterTime = s.iterStart ? fmtDur(Date.now() - s.iterStart) : '—';
199
- const pct = s.maxIter > 0 ? Math.round((s.iteration / s.maxIter) * 100) : 0;
200
- const filled = Math.round((pct / 100) * 8);
201
- const bar = `${ANSI_COLORS.green}${'\u2588'.repeat(filled)}${ANSI_COLORS.gray}${'\u2591'.repeat(8 - filled)}${RESET}`;
202
- const tok = fmtTok(s.tokensIn + s.tokensOut);
203
- const cur = s.agents.length > 0 ? s.agents[s.agents.length - 1] : '';
204
- let stats = `\x1b[7m ${elapsed} | Iter ${s.iteration}/${s.maxIter} \x1b[27m ${bar} \x1b[7m ${iterTime} | ${ANSI_COLORS.cyan}${s.tools}${RESET}\x1b[7m tools | ${ANSI_COLORS.yellow}${tok}${RESET}\x1b[7m tok`;
205
- if (cur) {
206
- const col = ANSI_COLORS[agentColor(cur)] || '';
207
- stats += ` | ${col}${BOLD}${cur}${RESET}\x1b[7m`;
197
+ if (s.idle) {
198
+ // Idle: static bar, no running timers
199
+ let stats = `\x1b[7m ${ANSI_COLORS.gray}IDLE${RESET}\x1b[7m waiting for input`;
200
+ if (s.pendingInputs.length > 0)
201
+ stats += ` | ${ANSI_COLORS.magenta}${s.pendingInputs.length} queued${RESET}\x1b[7m`;
202
+ if (s.paused)
203
+ stats += ` | ${ANSI_COLORS.yellow}⏸ PAUSED${RESET}\x1b[7m`;
204
+ stats += ` ${RESET}`;
205
+ lines.push(stats);
206
+ }
207
+ else {
208
+ const elapsed = fmtDur(Date.now() - s.loopStart);
209
+ const iterTime = s.iterStart ? fmtDur(Date.now() - s.iterStart) : '—';
210
+ const pct = s.maxIter > 0 ? Math.round((s.iteration / s.maxIter) * 100) : 0;
211
+ const filled = Math.round((pct / 100) * 8);
212
+ const bar = `${ANSI_COLORS.green}${'\u2588'.repeat(filled)}${ANSI_COLORS.gray}${'\u2591'.repeat(8 - filled)}${RESET}`;
213
+ const tok = fmtTok(s.tokensIn + s.tokensOut);
214
+ const cur = s.agents.length > 0 ? s.agents[s.agents.length - 1] : '';
215
+ let stats = `\x1b[7m ${elapsed} | Iter ${s.iteration}/${s.maxIter} \x1b[27m ${bar} \x1b[7m ${iterTime} | ${ANSI_COLORS.cyan}${s.tools}${RESET}\x1b[7m tools | ${ANSI_COLORS.yellow}${tok}${RESET}\x1b[7m tok`;
216
+ if (cur) {
217
+ const col = ANSI_COLORS[agentColor(cur)] || '';
218
+ stats += ` | ${col}${BOLD}${cur}${RESET}\x1b[7m`;
219
+ }
220
+ if (s.taskName)
221
+ stats += ` | ${ANSI_COLORS.cyan}${statusTrunc(s.taskName, 30)}${RESET}\x1b[7m`;
222
+ if (s.paused)
223
+ stats += ` | ${ANSI_COLORS.yellow}⏸ PAUSED${RESET}\x1b[7m`;
224
+ stats += ` ${RESET}`;
225
+ lines.push(stats);
208
226
  }
209
- if (s.taskName)
210
- stats += ` | ${ANSI_COLORS.cyan}${statusTrunc(s.taskName, 30)}${RESET}\x1b[7m`;
211
- if (s.paused)
212
- stats += ` | ${ANSI_COLORS.yellow}⏸ PAUSED${RESET}\x1b[7m`;
213
- stats += ` ${RESET}`;
214
- lines.push(stats);
215
227
  return lines;
216
228
  }
217
229
  /** Plain-text version for testing / non-ANSI contexts. */
@@ -224,21 +236,31 @@ export class TUI {
224
236
  lines.push(` [${s.pendingInputs.length} queued] ${items}${more}`);
225
237
  }
226
238
  lines.push(s.inputBuf ? ` › ${s.inputBuf}` : ' › type to send input');
227
- const elapsed = fmtDur(Date.now() - s.loopStart);
228
- const iterTime = s.iterStart ? fmtDur(Date.now() - s.iterStart) : '—';
229
- const pct = s.maxIter > 0 ? Math.round((s.iteration / s.maxIter) * 100) : 0;
230
- const filled = Math.round((pct / 100) * 8);
231
- const bar = '\u2588'.repeat(filled) + '\u2591'.repeat(8 - filled);
232
- const tok = fmtTok(s.tokensIn + s.tokensOut);
233
- const cur = s.agents.length > 0 ? s.agents[s.agents.length - 1] : '';
234
- let stats = ` ${elapsed} | Iter ${s.iteration}/${s.maxIter} ${bar} | ${iterTime} | ${s.tools} tools | ${tok} tok`;
235
- if (cur)
236
- stats += ` | ${cur}`;
237
- if (s.taskName)
238
- stats += ` | ${statusTrunc(s.taskName, 30)}`;
239
- if (s.paused)
240
- stats += ' | PAUSED';
241
- lines.push(stats);
239
+ if (s.idle) {
240
+ let stats = ' IDLE waiting for input';
241
+ if (s.pendingInputs.length > 0)
242
+ stats += ` | ${s.pendingInputs.length} queued`;
243
+ if (s.paused)
244
+ stats += ' | PAUSED';
245
+ lines.push(stats);
246
+ }
247
+ else {
248
+ const elapsed = fmtDur(Date.now() - s.loopStart);
249
+ const iterTime = s.iterStart ? fmtDur(Date.now() - s.iterStart) : '—';
250
+ const pct = s.maxIter > 0 ? Math.round((s.iteration / s.maxIter) * 100) : 0;
251
+ const filled = Math.round((pct / 100) * 8);
252
+ const bar = '\u2588'.repeat(filled) + '\u2591'.repeat(8 - filled);
253
+ const tok = fmtTok(s.tokensIn + s.tokensOut);
254
+ const cur = s.agents.length > 0 ? s.agents[s.agents.length - 1] : '';
255
+ let stats = ` ${elapsed} | Iter ${s.iteration}/${s.maxIter} ${bar} | ${iterTime} | ${s.tools} tools | ${tok} tok`;
256
+ if (cur)
257
+ stats += ` | ${cur}`;
258
+ if (s.taskName)
259
+ stats += ` | ${statusTrunc(s.taskName, 30)}`;
260
+ if (s.paused)
261
+ stats += ' | PAUSED';
262
+ lines.push(stats);
263
+ }
242
264
  return lines;
243
265
  }
244
266
  clearStatusArea() {
@@ -321,6 +343,10 @@ export class TUI {
321
343
  this.log(` ${BOLD}━━━ Iteration ${i}/${max} ${pct}% │ ${elapsed} elapsed ━━━${RESET}`);
322
344
  this.log('');
323
345
  }
346
+ setIdle(idle) {
347
+ this.state.idle = idle;
348
+ this.renderStatusBar();
349
+ }
324
350
  setTask(name, done, total) {
325
351
  this.state.taskName = name;
326
352
  this.state.tasksDone = done;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-claude-workspace",
3
- "version": "2.3.32",
3
+ "version": "2.3.34",
4
4
  "author": "",
5
5
  "repository": {
6
6
  "type": "git",