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 +24 -7
- package/dist/index.js +7 -0
- package/dist/scheduler/index.mjs +27 -3
- package/dist/scheduler/loop.mjs +1 -0
- package/dist/scheduler/ui/tui.mjs +61 -35
- package/package.json +1 -1
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
|
|
32
|
-
npx create-claude-workspace
|
|
33
|
-
npx create-claude-workspace
|
|
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
|
|
49
|
-
npx create-claude-workspace
|
|
50
|
-
npx create-claude-workspace
|
|
51
|
-
npx create-claude-workspace
|
|
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('');
|
package/dist/scheduler/index.mjs
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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)
|
package/dist/scheduler/loop.mjs
CHANGED
|
@@ -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}
|
|
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}
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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;
|