# maestro-docs-v2 > The **Maestro Worker Skill** is a set of CLI commands that Claude can use **from inside a session** to update task status and progress. - Author: subhang - Repository: subhangR/maestro - Version: 20260131165528 - Stars: 0 - Forks: 0 - Last Updated: 2026-02-06 - Source: https://github.com/subhangR/maestro - Web: https://mule.run/skillshub/@@subhangR/maestro~maestro-docs-v2:20260131165528 --- # Maestro Worker Skill ## Overview The **Maestro Worker Skill** is a set of CLI commands that Claude can use **from inside a session** to update task status and progress. Unlike hooks (which fire automatically), the skill commands are **explicitly invoked** by Claude or the user. ## Installation Location ``` /Users/subhang/Desktop/Projects/maestro/skills/maestro/ ``` ## Commands The skill provides four commands: 1. **`maestro update`** - Add progress update to task 2. **`maestro complete`** - Mark task as completed 3. **`maestro blocked`** - Mark task as blocked 4. **`maestro info`** - Show current task information ## Environment Variables Like hooks, commands receive context via environment variables: - `MAESTRO_PROJECT_ID` - Current project ID - `MAESTRO_TASK_ID` - Current task ID - `CLAUDE_SESSION_ID` - Current session ID ## Command: `maestro update` **Purpose**: Add a progress update to the task timeline. **Usage:** ```bash maestro update "Implemented login endpoint" ``` **Implementation:** ```javascript #!/usr/bin/env node import fetch from 'node-fetch'; const API_URL = 'http://localhost:3000/api'; async function update(message) { const taskId = process.env.MAESTRO_TASK_ID; const sessionId = process.env.CLAUDE_SESSION_ID; if (!taskId) { console.error('Error: No MAESTRO_TASK_ID set'); process.exit(1); } try { const taskRes = await fetch(`${API_URL}/tasks/${taskId}`); if (taskRes.ok) { const task = await taskRes.json(); await fetch(`${API_URL}/tasks/${taskId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ timeline: [ ...task.timeline, { type: 'update', timestamp: Date.now(), sessionId, message } ] }) }); } // Also add to session events if (sessionId) { await fetch(`${API_URL}/sessions/${sessionId}/events`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: `evt_${Date.now()}`, type: 'maestro_update', timestamp: Date.now(), message }) }); } console.log(`📝 Task updated: ${message}`); } catch (err) { console.error(`Error: ${err.message}`); process.exit(1); } } const message = process.argv.slice(2).join(' '); if (!message) { console.error('Usage: maestro update '); process.exit(1); } update(message); ``` **Example Output:** ``` 📝 Task updated: Implemented login endpoint ``` **Timeline Entry:** ```json { "type": "update", "timestamp": 1704072000000, "sessionId": "sess_xyz", "message": "Implemented login endpoint" } ``` ## Command: `maestro complete` **Purpose**: Mark task as completed with a summary. **Usage:** ```bash maestro complete "Auth system fully implemented with tests" ``` **Implementation:** ```javascript #!/usr/bin/env node import fetch from 'node-fetch'; const API_URL = 'http://localhost:3000/api'; async function complete(summary) { const taskId = process.env.MAESTRO_TASK_ID; const sessionId = process.env.CLAUDE_SESSION_ID; if (!taskId) { console.error('Error: No MAESTRO_TASK_ID set'); process.exit(1); } try { const taskRes = await fetch(`${API_URL}/tasks/${taskId}`); if (taskRes.ok) { const task = await taskRes.json(); await fetch(`${API_URL}/tasks/${taskId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status: 'completed', completedAt: Date.now(), timeline: [ ...task.timeline, { type: 'completed', timestamp: Date.now(), sessionId, summary } ] }) }); } // Add to session events if (sessionId) { await fetch(`${API_URL}/sessions/${sessionId}/events`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: `evt_${Date.now()}`, type: 'maestro_complete', timestamp: Date.now(), summary }) }); } console.log(`✅ Task completed: ${summary}`); } catch (err) { console.error(`Error: ${err.message}`); process.exit(1); } } const summary = process.argv.slice(2).join(' '); if (!summary) { console.error('Usage: maestro complete '); process.exit(1); } complete(summary); ``` **Example Output:** ``` ✅ Task completed: Auth system fully implemented with tests ``` **Task Updates:** - `status`: `"completed"` - `completedAt`: current timestamp - Timeline entry with type `"completed"` ## Command: `maestro blocked` **Purpose**: Mark task as blocked with a reason. **Usage:** ```bash maestro blocked "Need API key from backend team" ``` **Implementation:** ```javascript #!/usr/bin/env node import fetch from 'node-fetch'; const API_URL = 'http://localhost:3000/api'; async function blocked(reason) { const taskId = process.env.MAESTRO_TASK_ID; const sessionId = process.env.CLAUDE_SESSION_ID; if (!taskId) { console.error('Error: No MAESTRO_TASK_ID set'); process.exit(1); } try { const taskRes = await fetch(`${API_URL}/tasks/${taskId}`); if (taskRes.ok) { const task = await taskRes.json(); await fetch(`${API_URL}/tasks/${taskId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ timeline: [ ...task.timeline, { type: 'blocked', timestamp: Date.now(), sessionId, reason } ] }) }); } // Add to session events if (sessionId) { await fetch(`${API_URL}/sessions/${sessionId}/events`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: `evt_${Date.now()}`, type: 'maestro_blocked', timestamp: Date.now(), message: reason }) }); } console.log(`🚫 Task blocked: ${reason}`); } catch (err) { console.error(`Error: ${err.message}`); process.exit(1); } } const reason = process.argv.slice(2).join(' '); if (!reason) { console.error('Usage: maestro blocked '); process.exit(1); } blocked(reason); ``` **Example Output:** ``` 🚫 Task blocked: Need API key from backend team ``` ## Command: `maestro info` **Purpose**: Display current task information. **Usage:** ```bash maestro info ``` **Implementation:** ```javascript #!/usr/bin/env node import fetch from 'node-fetch'; const API_URL = 'http://localhost:3000/api'; async function info() { const taskId = process.env.MAESTRO_TASK_ID; const projectId = process.env.MAESTRO_PROJECT_ID; const sessionId = process.env.CLAUDE_SESSION_ID; if (!taskId) { console.error('Error: No MAESTRO_TASK_ID set'); process.exit(1); } try { const res = await fetch(`${API_URL}/tasks/${taskId}`); if (!res.ok) { throw new Error('Task not found'); } const task = await res.json(); console.log('\\n' + '─'.repeat(60)); console.log('TASK INFORMATION'); console.log('─'.repeat(60)); console.log(`Task ID: ${task.id}`); console.log(`Project ID: ${projectId || task.projectId}`); console.log(`Title: ${task.title}`); console.log(`Status: ${task.status}`); console.log(`Priority: ${task.priority || 'medium'}`); console.log(`Session ID: ${sessionId || 'N/A'}`); if (task.description) { console.log(`Description: ${task.description}`); } console.log(`Sessions: ${task.sessions.length}`); console.log(`Created: ${new Date(task.createdAt).toLocaleString()}`); console.log(`Updated: ${new Date(task.updatedAt).toLocaleString()}`); console.log('─'.repeat(60) + '\\n'); } catch (err) { console.error(`Error: ${err.message}`); process.exit(1); } } info(); ``` **Example Output:** ``` ──────────────────────────────────────────────────────────── TASK INFORMATION ──────────────────────────────────────────────────────────── Task ID: task_001 Project ID: proj_abc123 Title: Build authentication Status: in_progress Priority: high Session ID: sess_xyz Description: Implement JWT authentication Sessions: 2 Created: 1/1/2024, 10:00:00 AM Updated: 1/1/2024, 11:30:00 AM ──────────────────────────────────────────────────────────── ``` ## Typical Workflow ### Example: Claude Working on a Task ``` [Claude Code session starts] 1. Hook fires automatically → Session created → Task status → in_progress 2. Claude: "I'll implement the login endpoint first" Claude runs: maestro update "Starting with login endpoint" → Timeline updated → UI shows progress 3. Claude edits auth.ts, runs tests → Hooks track tool usage automatically 4. Claude: "Login endpoint is done" Claude runs: maestro update "Login endpoint complete" → Timeline updated 5. Claude: "Need bcrypt library installed" Claude runs: maestro blocked "Need to install bcrypt package" → Timeline updated → User can see blocker in UI 6. [User installs bcrypt] 7. Claude: "All done! Auth fully implemented" Claude runs: maestro complete "JWT auth with login/logout working" → Task marked complete → UI updates [Session ends] 8. Hook fires automatically → Session marked complete ``` ## Dependencies Skills require `node-fetch`: ```json { "name": "@maestro/skill", "type": "module", "bin": { "maestro": "./commands/index.js" }, "dependencies": { "node-fetch": "^3.3.0" } } ``` ## Error Handling Unlike hooks, skill commands **show errors** to the user: ```javascript if (!taskId) { console.error('Error: No MAESTRO_TASK_ID set'); process.exit(1); } ``` **Why?** - Commands are explicitly run by user/Claude - Errors indicate misconfiguration - User should know if command failed ## Installation The skill is automatically available when Maestro CLI launches Claude: ```bash maestro # Launches Claude with MAESTRO_* env vars # maestro commands work inside session ``` ## Testing Test commands manually: ```bash export MAESTRO_TASK_ID=task_001 export CLAUDE_SESSION_ID=sess_test maestro info maestro update "Test message" maestro complete "Test complete" ``` ## Relationship to Hooks & API ``` Skill Command ↓ REST API (fetch) ↓ Server (Express) ↓ Storage (in-memory + async persist) ↓ WebSocket broadcast ↓ UI updates instantly ``` Same flow as hooks - ensures consistency. ## Customization You can add custom commands: ```javascript // skills/maestro/commands/screenshot.js #!/usr/bin/env node async function screenshot() { const taskId = process.env.MAESTRO_TASK_ID; // ... take screenshot, upload, add to task } screenshot(); ``` Then use: ```bash maestro screenshot ```