chore: bump version to 0.1394
@@ -38,7 +38,7 @@ Claudeman is a Claude Code session manager with a web interface and autonomous R
|
|||||||
|
|
||||||
When adding new features, always ask: "Will this maintain responsiveness with 20 sessions and 50 agent windows?"
|
When adding new features, always ask: "Will this maintain responsiveness with 20 sessions and 50 agent windows?"
|
||||||
|
|
||||||
**Version**: 0.1393 (must match `package.json`)
|
**Version**: 0.1394 (must match `package.json`)
|
||||||
|
|
||||||
**Tech Stack**: TypeScript (ES2022/NodeNext, strict mode), Node.js, Fastify, Server-Sent Events, node-pty
|
**Tech Stack**: TypeScript (ES2022/NodeNext, strict mode), Node.js, Fastify, Server-Sent Events, node-pty
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "claudeman",
|
"name": "claudeman",
|
||||||
"version": "0.1393",
|
"version": "0.1394",
|
||||||
"description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence",
|
"description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
@@ -1202,18 +1202,24 @@ export class Session extends EventEmitter {
|
|||||||
* the response.
|
* the response.
|
||||||
*
|
*
|
||||||
* @param prompt - The prompt text to send to Claude
|
* @param prompt - The prompt text to send to Claude
|
||||||
|
* @param options - Optional configuration
|
||||||
|
* @param options.model - Model to use ('opus', 'sonnet', or full model name). Defaults to default model.
|
||||||
|
* @param options.onProgress - Callback for progress updates (token count, status)
|
||||||
* @returns Promise resolving to the result text and total cost in USD
|
* @returns Promise resolving to the result text and total cost in USD
|
||||||
* @throws {Error} If a process is already running in this session
|
* @throws {Error} If a process is already running in this session
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* ```typescript
|
* ```typescript
|
||||||
* const session = new Session({ workingDir: '/project' });
|
* const session = new Session({ workingDir: '/project' });
|
||||||
* const { result, cost } = await session.runPrompt('Explain this code');
|
* const { result, cost } = await session.runPrompt('Explain this code', { model: 'opus' });
|
||||||
* console.log(`Response: ${result}`);
|
* console.log(`Response: ${result}`);
|
||||||
* console.log(`Cost: $${cost.toFixed(4)}`);
|
* console.log(`Cost: $${cost.toFixed(4)}`);
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
async runPrompt(prompt: string): Promise<{ result: string; cost: number }> {
|
async runPrompt(
|
||||||
|
prompt: string,
|
||||||
|
options?: { model?: string; onProgress?: (info: { tokens?: number; status?: string }) => void }
|
||||||
|
): Promise<{ result: string; cost: number }> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (this.ptyProcess) {
|
if (this.ptyProcess) {
|
||||||
reject(new Error('Session already has a running process'));
|
reject(new Error('Session already has a running process'));
|
||||||
@@ -1235,14 +1241,20 @@ export class Session extends EventEmitter {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Spawn claude in a real PTY
|
// Spawn claude in a real PTY
|
||||||
console.log('[Session] Spawning PTY for claude with prompt:', prompt.substring(0, 50));
|
const model = options?.model;
|
||||||
|
console.log('[Session] Spawning PTY for claude with prompt:', prompt.substring(0, 50), model ? `(model: ${model})` : '');
|
||||||
|
|
||||||
this.ptyProcess = pty.spawn('claude', [
|
const args = [
|
||||||
'-p',
|
'-p',
|
||||||
'--dangerously-skip-permissions',
|
'--dangerously-skip-permissions',
|
||||||
'--output-format', 'stream-json',
|
'--output-format', 'stream-json',
|
||||||
prompt
|
];
|
||||||
], {
|
if (model) {
|
||||||
|
args.push('--model', model);
|
||||||
|
}
|
||||||
|
args.push(prompt);
|
||||||
|
|
||||||
|
this.ptyProcess = pty.spawn('claude', args, {
|
||||||
name: 'xterm-256color',
|
name: 'xterm-256color',
|
||||||
cols: 120,
|
cols: 120,
|
||||||
rows: 40,
|
rows: 40,
|
||||||
|
|||||||
@@ -2635,34 +2635,67 @@ class ClaudemanApp {
|
|||||||
async generatePlan() {
|
async generatePlan() {
|
||||||
const config = this.ralphWizardConfig;
|
const config = this.ralphWizardConfig;
|
||||||
|
|
||||||
// Stop any existing timer
|
// Stop any existing timers
|
||||||
if (this.planLoadingTimer) {
|
if (this.planLoadingTimer) {
|
||||||
clearInterval(this.planLoadingTimer);
|
clearInterval(this.planLoadingTimer);
|
||||||
this.planLoadingTimer = null;
|
this.planLoadingTimer = null;
|
||||||
}
|
}
|
||||||
|
if (this.planPhaseTimer) {
|
||||||
|
clearInterval(this.planPhaseTimer);
|
||||||
|
this.planPhaseTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
// Show loading state
|
// Show loading state
|
||||||
document.getElementById('planGenerationError')?.classList.add('hidden');
|
document.getElementById('planGenerationError')?.classList.add('hidden');
|
||||||
document.getElementById('planEditor')?.classList.add('hidden');
|
document.getElementById('planEditor')?.classList.add('hidden');
|
||||||
document.getElementById('planGenerationLoading')?.classList.remove('hidden');
|
document.getElementById('planGenerationLoading')?.classList.remove('hidden');
|
||||||
|
|
||||||
// Start elapsed time display
|
// Animated progress phases
|
||||||
|
const phases = [
|
||||||
|
{ time: 0, title: 'Starting Opus 4.5...', hint: 'Initializing deep reasoning model' },
|
||||||
|
{ time: 3, title: 'Analyzing task requirements...', hint: 'Understanding the scope and complexity' },
|
||||||
|
{ time: 8, title: 'Identifying components...', hint: 'Breaking down into modules and features' },
|
||||||
|
{ time: 15, title: 'Planning TDD approach...', hint: 'Designing test-first implementation strategy' },
|
||||||
|
{ time: 25, title: 'Generating implementation steps...', hint: 'Creating detailed action items with tests' },
|
||||||
|
{ time: 40, title: 'Adding verification checkpoints...', hint: 'Ensuring each phase has validation' },
|
||||||
|
{ time: 55, title: 'Reviewing for completeness...', hint: 'Checking all requirements are covered' },
|
||||||
|
{ time: 70, title: 'Finalizing plan...', hint: 'Organizing and prioritizing steps' },
|
||||||
|
{ time: 90, title: 'Still working...', hint: 'Complex tasks take longer - hang tight!' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Start elapsed time and phase display
|
||||||
this.planLoadingStartTime = Date.now();
|
this.planLoadingStartTime = Date.now();
|
||||||
const timeEl = document.getElementById('planLoadingTime');
|
const timeEl = document.getElementById('planLoadingTime');
|
||||||
if (timeEl) timeEl.textContent = '0s';
|
const titleEl = document.getElementById('planLoadingTitle');
|
||||||
|
const hintEl = document.getElementById('planLoadingHint');
|
||||||
|
|
||||||
|
if (timeEl) timeEl.textContent = '0s';
|
||||||
|
if (titleEl) titleEl.textContent = phases[0].title;
|
||||||
|
if (hintEl) hintEl.textContent = phases[0].hint;
|
||||||
|
|
||||||
|
let currentPhaseIndex = 0;
|
||||||
this.planLoadingTimer = setInterval(() => {
|
this.planLoadingTimer = setInterval(() => {
|
||||||
const elapsed = Math.floor((Date.now() - this.planLoadingStartTime) / 1000);
|
const elapsed = Math.floor((Date.now() - this.planLoadingStartTime) / 1000);
|
||||||
if (timeEl) timeEl.textContent = `${elapsed}s`;
|
if (timeEl) timeEl.textContent = `${elapsed}s`;
|
||||||
|
|
||||||
|
// Update phase based on elapsed time
|
||||||
|
for (let i = phases.length - 1; i >= 0; i--) {
|
||||||
|
if (elapsed >= phases[i].time && i > currentPhaseIndex) {
|
||||||
|
currentPhaseIndex = i;
|
||||||
|
if (titleEl) titleEl.textContent = phases[i].title;
|
||||||
|
if (hintEl) hintEl.textContent = phases[i].hint;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
// Determine max items based on detail level
|
// Determine max items based on detail level
|
||||||
const detailConfig = {
|
const detailConfig = {
|
||||||
brief: { maxItems: 5, includeTests: false },
|
brief: { maxItems: 10 },
|
||||||
standard: { maxItems: 8, includeTests: true },
|
standard: { maxItems: 15 },
|
||||||
detailed: { maxItems: 12, includeTests: true },
|
detailed: { maxItems: 20 },
|
||||||
};
|
};
|
||||||
const { maxItems, includeTests } = detailConfig[config.planDetailLevel] || detailConfig.standard;
|
const { maxItems } = detailConfig[config.planDetailLevel] || detailConfig.standard;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/generate-plan', {
|
const res = await fetch('/api/generate-plan', {
|
||||||
@@ -2671,7 +2704,6 @@ class ClaudemanApp {
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
taskDescription: config.taskDescription,
|
taskDescription: config.taskDescription,
|
||||||
maxItems,
|
maxItems,
|
||||||
includeTests,
|
|
||||||
detailLevel: config.planDetailLevel,
|
detailLevel: config.planDetailLevel,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
@@ -2838,6 +2870,16 @@ class ClaudemanApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
skipPlanGeneration() {
|
skipPlanGeneration() {
|
||||||
|
// Stop any running timers
|
||||||
|
if (this.planLoadingTimer) {
|
||||||
|
clearInterval(this.planLoadingTimer);
|
||||||
|
this.planLoadingTimer = null;
|
||||||
|
}
|
||||||
|
if (this.planPhaseTimer) {
|
||||||
|
clearInterval(this.planPhaseTimer);
|
||||||
|
this.planPhaseTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
this.ralphWizardConfig.skipPlanGeneration = true;
|
this.ralphWizardConfig.skipPlanGeneration = true;
|
||||||
this.ralphWizardConfig.planGenerated = false;
|
this.ralphWizardConfig.planGenerated = false;
|
||||||
this.ralphWizardConfig.generatedPlan = null;
|
this.ralphWizardConfig.generatedPlan = null;
|
||||||
|
|||||||
@@ -1011,11 +1011,12 @@
|
|||||||
<div class="plan-loading-content">
|
<div class="plan-loading-content">
|
||||||
<div class="plan-spinner"></div>
|
<div class="plan-spinner"></div>
|
||||||
<div class="plan-loading-text">
|
<div class="plan-loading-text">
|
||||||
<span class="plan-loading-title">Generating implementation plan...</span>
|
<span class="plan-loading-title" id="planLoadingTitle">Starting Opus 4.5...</span>
|
||||||
<span class="plan-loading-time" id="planLoadingTime">0s</span>
|
<span class="plan-loading-time" id="planLoadingTime">0s</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="plan-loading-hint">Claude is breaking down your task into actionable steps with tests</p>
|
<p class="plan-loading-hint" id="planLoadingHint">Initializing deep reasoning model</p>
|
||||||
|
<button class="btn-toolbar btn-sm plan-skip-btn" onclick="app.skipPlanGeneration()">Skip</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Error state -->
|
<!-- Error state -->
|
||||||
|
|||||||
@@ -5344,7 +5344,7 @@ kbd {
|
|||||||
|
|
||||||
/* Wizard Pages */
|
/* Wizard Pages */
|
||||||
.wizard-page {
|
.wizard-page {
|
||||||
min-height: 280px;
|
min-height: 180px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wizard-page.hidden {
|
.wizard-page.hidden {
|
||||||
@@ -5482,7 +5482,15 @@ kbd {
|
|||||||
.plan-loading-hint {
|
.plan-loading-hint {
|
||||||
color: var(--text-dim);
|
color: var(--text-dim);
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
margin: 0;
|
margin: 0 0 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-skip-btn {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-skip-btn:hover {
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plan-error-msg {
|
.plan-error-msg {
|
||||||
@@ -5744,11 +5752,11 @@ kbd {
|
|||||||
background: var(--bg-dark);
|
background: var(--bg-dark);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 0.75rem;
|
padding: 0.5rem 0.75rem;
|
||||||
font-family: 'SF Mono', Monaco, 'Courier New', monospace;
|
font-family: 'SF Mono', Monaco, 'Courier New', monospace;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
color: var(--text-dim);
|
color: var(--text-dim);
|
||||||
max-height: 200px;
|
max-height: 120px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
|
|||||||
@@ -2056,6 +2056,7 @@ export class WebServer extends EventEmitter {
|
|||||||
interface GeneratePlanRequest {
|
interface GeneratePlanRequest {
|
||||||
taskDescription: string;
|
taskDescription: string;
|
||||||
maxItems?: number;
|
maxItems?: number;
|
||||||
|
detailLevel?: 'brief' | 'standard' | 'detailed';
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PlanItem {
|
interface PlanItem {
|
||||||
@@ -2064,7 +2065,11 @@ export class WebServer extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.app.post('/api/generate-plan', async (req): Promise<ApiResponse> => {
|
this.app.post('/api/generate-plan', async (req): Promise<ApiResponse> => {
|
||||||
const { taskDescription, maxItems = 8 } = req.body as GeneratePlanRequest;
|
const {
|
||||||
|
taskDescription,
|
||||||
|
maxItems = 12,
|
||||||
|
detailLevel = 'standard'
|
||||||
|
} = req.body as GeneratePlanRequest;
|
||||||
|
|
||||||
if (!taskDescription || typeof taskDescription !== 'string') {
|
if (!taskDescription || typeof taskDescription !== 'string') {
|
||||||
return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Task description is required');
|
return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Task description is required');
|
||||||
@@ -2074,26 +2079,86 @@ export class WebServer extends EventEmitter {
|
|||||||
return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Task description too long (max 10000 chars)');
|
return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Task description too long (max 10000 chars)');
|
||||||
}
|
}
|
||||||
|
|
||||||
const prompt = `Break down this task into ${maxItems} or fewer actionable implementation steps.
|
// Build sophisticated prompt based on Ralph Wiggum methodology
|
||||||
|
const detailConfig = {
|
||||||
|
brief: { minSteps: 5, maxSteps: maxItems, testDepth: 'basic' },
|
||||||
|
standard: { minSteps: 8, maxSteps: maxItems, testDepth: 'thorough' },
|
||||||
|
detailed: { minSteps: 12, maxSteps: Math.max(maxItems, 15), testDepth: 'comprehensive' },
|
||||||
|
};
|
||||||
|
const config = detailConfig[detailLevel] || detailConfig.standard;
|
||||||
|
|
||||||
TASK:
|
const prompt = `You are an expert software architect breaking down a task into a thorough implementation plan.
|
||||||
|
|
||||||
|
## TASK TO IMPLEMENT
|
||||||
${taskDescription}
|
${taskDescription}
|
||||||
|
|
||||||
OUTPUT FORMAT:
|
## YOUR MISSION
|
||||||
Return ONLY a JSON array. Each item must have:
|
Create a detailed, actionable implementation plan following Test-Driven Development (TDD) methodology.
|
||||||
- content: specific, actionable step (verb phrase, 5-80 chars)
|
Think deeply about:
|
||||||
- priority: "P0" (critical path), "P1" (standard), "P2" (nice-to-have), or null
|
- What are all the components needed?
|
||||||
|
- What could go wrong? Add defensive steps.
|
||||||
|
- How will we verify each part works?
|
||||||
|
- What edge cases need handling?
|
||||||
|
|
||||||
Example output:
|
## PLAN STRUCTURE (${config.minSteps}-${config.maxSteps} steps)
|
||||||
[{"content": "Create auth database schema", "priority": "P0"}, {"content": "Implement JWT token generation", "priority": "P1"}]
|
|
||||||
|
|
||||||
RULES:
|
Your plan MUST include these phases in order:
|
||||||
- Start each step with a verb (Create, Implement, Add, Write, Configure, etc.)
|
|
||||||
- Keep steps focused and achievable in one work session
|
|
||||||
- Order by logical dependency (do first things first)
|
|
||||||
- Use P0 sparingly for truly critical items`;
|
|
||||||
|
|
||||||
// Create temporary session for the AI call
|
### Phase 1: Foundation & Setup
|
||||||
|
- Project structure, dependencies, configuration
|
||||||
|
- Database schemas, type definitions, interfaces
|
||||||
|
|
||||||
|
### Phase 2: Core Implementation (TDD Cycle)
|
||||||
|
For EACH feature:
|
||||||
|
1. Write failing tests first (unit tests)
|
||||||
|
2. Implement the feature
|
||||||
|
3. Run tests, debug until passing
|
||||||
|
4. Refactor if needed
|
||||||
|
|
||||||
|
### Phase 3: Integration & Edge Cases
|
||||||
|
- Integration tests for feature interactions
|
||||||
|
- Edge case handling (errors, boundaries, invalid input)
|
||||||
|
- Error messages and user feedback
|
||||||
|
|
||||||
|
### Phase 4: Verification & Hardening
|
||||||
|
- Run full test suite
|
||||||
|
- Fix any failing tests
|
||||||
|
- Add missing test coverage
|
||||||
|
- Final verification that ALL requirements are met
|
||||||
|
|
||||||
|
## OUTPUT FORMAT
|
||||||
|
Return ONLY a JSON array. Each item:
|
||||||
|
- content: specific action (verb phrase, 15-120 chars, be descriptive!)
|
||||||
|
- priority: "P0" (critical/blocking), "P1" (required), "P2" (enhancement)
|
||||||
|
|
||||||
|
## EXAMPLE OUTPUT
|
||||||
|
[
|
||||||
|
{"content": "Create project structure with src/, tests/, and config directories", "priority": "P0"},
|
||||||
|
{"content": "Define TypeScript interfaces for User, Session, and AuthToken types", "priority": "P0"},
|
||||||
|
{"content": "Write failing unit tests for password hashing (valid password, empty, too short)", "priority": "P0"},
|
||||||
|
{"content": "Implement password hashing with bcrypt, configurable salt rounds", "priority": "P0"},
|
||||||
|
{"content": "Run password tests and debug until all pass", "priority": "P0"},
|
||||||
|
{"content": "Write failing tests for JWT token generation and validation", "priority": "P0"},
|
||||||
|
{"content": "Implement JWT service with access/refresh token support", "priority": "P0"},
|
||||||
|
{"content": "Run JWT tests and verify token expiration handling works", "priority": "P0"},
|
||||||
|
{"content": "Write integration tests for login flow (valid creds, invalid, locked account)", "priority": "P1"},
|
||||||
|
{"content": "Implement login endpoint with rate limiting and audit logging", "priority": "P1"},
|
||||||
|
{"content": "Add error handling for network failures and database timeouts", "priority": "P1"},
|
||||||
|
{"content": "Run full test suite and fix any failures", "priority": "P1"},
|
||||||
|
{"content": "Verify all original requirements are implemented and tested", "priority": "P1"}
|
||||||
|
]
|
||||||
|
|
||||||
|
## CRITICAL RULES
|
||||||
|
1. EVERY implementation step should have a corresponding test step BEFORE it
|
||||||
|
2. Include "Run tests and debug/fix" steps after implementation blocks
|
||||||
|
3. Be SPECIFIC - not "Add tests" but "Write tests for X covering Y and Z"
|
||||||
|
4. Think about what could fail and add defensive steps
|
||||||
|
5. End with verification that ALL original requirements are met
|
||||||
|
6. Use P0 for foundation and core features, P1 for required work, P2 for nice-to-have
|
||||||
|
|
||||||
|
NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||||
|
|
||||||
|
// Create temporary session for the AI call using Opus 4.5 for deep reasoning
|
||||||
const session = new Session({
|
const session = new Session({
|
||||||
workingDir: process.cwd(),
|
workingDir: process.cwd(),
|
||||||
screenManager: this.screenManager,
|
screenManager: this.screenManager,
|
||||||
@@ -2101,8 +2166,11 @@ RULES:
|
|||||||
mode: 'claude',
|
mode: 'claude',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Use Opus 4.5 for plan generation (better reasoning)
|
||||||
|
const modelToUse = 'opus';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { result, cost } = await session.runPrompt(prompt);
|
const { result, cost } = await session.runPrompt(prompt, { model: modelToUse });
|
||||||
|
|
||||||
// Parse JSON from result
|
// Parse JSON from result
|
||||||
const jsonMatch = result.match(/\[[\s\S]*\]/);
|
const jsonMatch = result.match(/\[[\s\S]*\]/);
|
||||||
|
|||||||
|
After Width: | Height: | Size: 207 KiB |
|
After Width: | Height: | Size: 206 KiB |
|
After Width: | Height: | Size: 201 KiB |
|
After Width: | Height: | Size: 201 KiB |
|
After Width: | Height: | Size: 202 KiB |
|
After Width: | Height: | Size: 216 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 188 KiB |