@gongfu/claude-integration
Version:
Claude Code integration toolkit for Gongfu workflow system
1 lines • 63.3 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts","../src/core/claude-controller.ts","../src/utils/logger.ts","../src/core/claude-monitor.ts","../src/core/claude-process-manager.ts","../src/utils/output-parser.ts","../src/core/claude-activity-handler.ts"],"sourcesContent":["/**\n * @gongfu/claude-integration\n * Claude Code integration toolkit for Gongfu workflow system\n */\n\n// Core classes\nexport { ClaudeController } from './core/claude-controller';\nexport { ClaudeMonitor } from './core/claude-monitor';\nexport { ClaudeProcessManager } from './core/claude-process-manager';\nexport { ClaudeActivityHandler } from './core/claude-activity-handler';\n\n// Types\nexport type {\n ClaudeControllerOptions,\n ClaudeStatus,\n ClaudeProcessOptions,\n ClaudeOutput,\n ClaudeMonitorOptions,\n ClaudeActivity,\n ActivitySummary,\n HookEvent,\n ParsedOutput\n} from './types';\n\n// Utilities\nexport { ClaudeOutputParser } from './utils/output-parser';\nexport { createLogger } from './utils/logger';\n\n// Constants\nexport const CLAUDE_DIR = '.claude';\nexport const CLAUDE_LOGS_DIR = '.claude/logs';\nexport const CLAUDE_HOOKS_FILE = '.claude/settings.local.json';\nexport const CLAUDE_TASK_FILE = '.claude/current-task.md';\n\n// Version\nexport const version = '0.1.0';","import * as fs from 'fs-extra';\nimport * as path from 'path';\nimport { spawn, ChildProcess } from 'child_process';\nimport { EventEmitter } from 'events';\nimport { logger } from '../utils/logger';\nimport { ClaudeControllerOptions, ClaudeStatus, TaskOptions } from '../types';\nimport { v4 as uuidv4 } from 'uuid';\n\ninterface Task {\n id: string;\n content: string;\n priority: 'urgent' | 'high' | 'medium' | 'low';\n status: string;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport class ClaudeController extends EventEmitter {\n private projectRoot: string;\n private claudeDir: string;\n private currentTaskFile: string;\n private process?: ChildProcess;\n private outputBuffer: string = '';\n\n constructor(private options: ClaudeControllerOptions = {}) {\n super();\n this.projectRoot = options.projectRoot || process.cwd();\n this.claudeDir = path.join(this.projectRoot, '.claude');\n this.currentTaskFile = path.join(this.claudeDir, 'current-task.md');\n }\n\n /**\n * Assign a task to Claude Code\n */\n async assignTask(taskDescription: string, options: TaskOptions = {}): Promise<string> {\n await fs.ensureDir(this.claudeDir);\n\n const taskId = uuidv4();\n const task: Task = {\n id: taskId,\n content: taskDescription,\n priority: options.priority || 'medium',\n status: 'in_progress',\n createdAt: new Date(),\n updatedAt: new Date()\n };\n\n // Create task instruction file\n const taskContent = this.createTaskInstruction(task);\n await fs.writeFile(this.currentTaskFile, taskContent);\n\n // Create a signal file for Claude to detect\n const signalFile = path.join(this.claudeDir, 'new-task.signal');\n await fs.writeFile(signalFile, JSON.stringify({\n taskId,\n timestamp: new Date().toISOString()\n }));\n\n // Remove signal file after a short delay\n setTimeout(() => {\n fs.remove(signalFile).catch(() => {});\n }, 5000);\n\n logger.info(`Task assigned: ${taskId}`);\n this.emit('task-assigned', { taskId, task });\n\n return taskId;\n }\n\n /**\n * Create task instruction content\n */\n private createTaskInstruction(task: Task): string {\n return `# 🎯 Current Task\n\n## Task ID: ${task.id}\n\n## Description\n${task.content}\n\n## Priority: ${task.priority}\n\n## Instructions\nPlease complete this task following these guidelines:\n\n1. **Understand**: Analyze the requirements carefully\n2. **Plan**: Create a step-by-step approach\n3. **Implement**: Write clean, maintainable code\n4. **Test**: Ensure the solution works correctly\n5. **Document**: Update relevant documentation\n\n## Completion\nWhen you finish this task:\n1. Run tests to ensure everything works\n2. Update the task status using the todo tool\n3. Commit your changes with a descriptive message\n\n## Context\n- Project: ${path.basename(this.projectRoot)}\n- Started: ${new Date().toISOString()}\n- Workflow integration enabled\n\n---\n*This task was assigned by the Gongfu workflow system*\n`;\n }\n\n /**\n * Start Claude Code in background mode\n */\n async startInBackground(): Promise<void> {\n if (this.process) {\n throw new Error('Claude Code is already running');\n }\n\n // Create a wrapper script to capture output\n const wrapperScript = path.join(this.claudeDir, 'claude-wrapper.sh');\n const wrapperContent = `#!/bin/bash\ncd \"${this.projectRoot}\"\nexport GONGFU_TASK_MODE=1\nclaude code 2>&1 | tee -a \"${path.join(this.claudeDir, 'logs', 'claude-output.log')}\"\n`;\n\n await fs.writeFile(wrapperScript, wrapperContent);\n await fs.chmod(wrapperScript, '755');\n\n // Start Claude Code in background\n this.process = spawn('bash', [wrapperScript], {\n detached: true,\n stdio: ['ignore', 'pipe', 'pipe'],\n cwd: this.projectRoot\n });\n\n if (this.options.logOutput) {\n this.process.stdout?.on('data', (data) => {\n this.outputBuffer += data.toString();\n this.parseOutput(data.toString());\n });\n\n this.process.stderr?.on('data', (data) => {\n logger.error('Claude Code error:', data.toString());\n });\n }\n\n this.process.on('exit', (code) => {\n logger.info(`Claude Code exited with code ${code}`);\n this.process = undefined;\n this.emit('process-exit', code);\n });\n\n // Detach the process\n this.process.unref();\n\n logger.info('Claude Code started in background');\n }\n\n /**\n * Parse Claude Code output\n */\n private parseOutput(output: string): void {\n // Try to extract meaningful information from the output\n const lines = output.split('\\n');\n \n for (const line of lines) {\n // Detect tool usage\n if (line.includes('Running:') || line.includes('Executing:')) {\n this.emit('tool-use', {\n timestamp: new Date().toISOString(),\n command: line\n });\n }\n\n // Detect file changes\n if (line.includes('Modified:') || line.includes('Created:')) {\n this.emit('file-change', {\n timestamp: new Date().toISOString(),\n file: line\n });\n }\n\n // Detect thinking/planning\n if (line.includes('Thinking:') || line.includes('Planning:')) {\n this.emit('thinking', {\n timestamp: new Date().toISOString(),\n thought: line\n });\n }\n }\n }\n\n /**\n * Get Claude Code status\n */\n async getStatus(): Promise<ClaudeStatus> {\n const status: ClaudeStatus = {\n isActive: false,\n recentActivity: []\n };\n\n // Check if process is running\n if (this.process && !this.process.killed) {\n status.isActive = true;\n status.processId = this.process.pid;\n }\n\n // Check current task\n if (await fs.pathExists(this.currentTaskFile)) {\n const content = await fs.readFile(this.currentTaskFile, 'utf-8');\n const idMatch = content.match(/## Task ID: (.+)/);\n const descMatch = content.match(/## Description\\n(.+)/);\n const startMatch = content.match(/- Started: (.+)/);\n\n if (idMatch && descMatch) {\n status.currentTask = {\n id: idMatch[1],\n content: descMatch[1],\n startedAt: startMatch ? startMatch[1] : new Date().toISOString()\n };\n }\n }\n\n // Read recent activity from logs\n const activityLog = path.join(this.claudeDir, 'logs', 'activity.log');\n if (await fs.pathExists(activityLog)) {\n const content = await fs.readFile(activityLog, 'utf-8');\n const lines = content.split('\\n').filter(line => line.trim()).slice(-10);\n \n for (const line of lines) {\n const match = line.match(/^\\[(.+?)\\] (\\w+): (.+)$/);\n if (match) {\n status.recentActivity.push({\n timestamp: match[1],\n type: match[2],\n description: match[3]\n });\n }\n }\n }\n\n return status;\n }\n\n /**\n * Setup enhanced hooks for Claude Code\n */\n async setupHooks(enhanced: boolean = false): Promise<void> {\n const settingsPath = path.join(this.claudeDir, 'settings.local.json');\n let settings: any = {};\n\n if (await fs.pathExists(settingsPath)) {\n settings = await fs.readJson(settingsPath);\n }\n\n // Ensure logs directory\n const logsDir = path.join(this.claudeDir, 'logs');\n await fs.ensureDir(logsDir);\n\n if (enhanced) {\n // Enhanced hooks for full monitoring\n settings.hooks = {\n ...settings.hooks,\n PreToolUse: [\n {\n matcher: '.*',\n hooks: [{\n type: 'command',\n command: `echo \"[$(date '+%Y-%m-%d %H:%M:%S')] TOOL_START: {tool} | {args}\" >> \"${path.join(logsDir, 'activity.log')}\"`,\n timeout: 1\n }]\n }\n ],\n PostToolUse: [\n ...(settings.hooks?.PostToolUse || []),\n {\n matcher: '.*',\n hooks: [{\n type: 'command',\n command: `echo \"[$(date '+%Y-%m-%d %H:%M:%S')] TOOL_END: {tool} | Success: {success}\" >> \"${path.join(logsDir, 'activity.log')}\"`,\n timeout: 1\n }]\n },\n {\n matcher: '^(Edit|Write|MultiEdit)',\n hooks: [{\n type: 'command',\n command: `cd \"${this.projectRoot}\" && npx @gongfu/workflow-cli claude log --type file-change --data \"{file_path}\"`,\n timeout: 2\n }]\n }\n ],\n Start: [{\n hooks: [{\n type: 'command',\n command: `cd \"${this.projectRoot}\" && npx @gongfu/workflow-cli claude log --type session-start`,\n timeout: 1\n }]\n }],\n Stop: [{\n hooks: [{\n type: 'command',\n command: `cd \"${this.projectRoot}\" && npx @gongfu/workflow-cli claude log --type session-end`,\n timeout: 1\n }]\n }]\n };\n }\n\n await fs.writeJson(settingsPath, settings, { spaces: 2 });\n logger.info('Claude Code hooks configured');\n }\n\n /**\n * Stop Claude Code\n */\n async stop(): Promise<void> {\n if (this.process && !this.process.killed) {\n this.process.kill('SIGTERM');\n logger.info('Claude Code stopped');\n }\n }\n}","import pc from 'picocolors';\n\nexport interface Logger {\n info: (message: string, ...args: any[]) => void;\n warn: (message: string, ...args: any[]) => void;\n error: (message: string, ...args: any[]) => void;\n debug: (message: string, ...args: any[]) => void;\n}\n\nexport function createLogger(prefix?: string): Logger {\n const p = prefix ? `[${prefix}] ` : '';\n \n return {\n info: (message: string, ...args: any[]) => {\n console.log(pc.blue(p + message), ...args);\n },\n warn: (message: string, ...args: any[]) => {\n console.warn(pc.yellow(p + message), ...args);\n },\n error: (message: string, ...args: any[]) => {\n console.error(pc.red(p + message), ...args);\n },\n debug: (message: string, ...args: any[]) => {\n if (process.env.DEBUG) {\n console.log(pc.gray(p + message), ...args);\n }\n }\n };\n}\n\nexport const logger = createLogger('claude-integration');","import { EventEmitter } from 'events';\nimport * as fs from 'fs-extra';\nimport * as path from 'path';\nimport { watch, FSWatcher } from 'chokidar';\nimport { logger } from '../utils/logger';\nimport { ClaudeActivity, ClaudeMonitorOptions } from '../types';\nimport express from 'express';\nimport { Server } from 'http';\nimport { v4 as uuidv4 } from 'uuid';\n\n\nexport class ClaudeMonitor extends EventEmitter {\n private projectRoot: string;\n private claudeDir: string;\n private logsDir: string;\n private watcher?: FSWatcher;\n private sseServer?: Server;\n private sseClients: Set<any> = new Set();\n private activityBuffer: ClaudeActivity[] = [];\n private maxBufferSize = 1000;\n\n constructor(private options: ClaudeMonitorOptions = {}) {\n super();\n this.projectRoot = options.projectRoot || process.cwd();\n this.claudeDir = path.join(this.projectRoot, '.claude');\n this.logsDir = path.join(this.claudeDir, 'logs');\n }\n\n /**\n * Start monitoring Claude Code activity\n */\n async start(): Promise<void> {\n // Ensure logs directory exists\n await fs.ensureDir(this.logsDir);\n\n // Start file watcher\n this.startFileWatcher();\n\n // Start SSE server if requested\n if (this.options.outputFormat === 'sse') {\n await this.startSSEServer();\n }\n\n // Parse existing logs\n await this.parseExistingLogs();\n\n logger.info('Claude monitor started');\n }\n\n /**\n * Stop monitoring\n */\n async stop(): Promise<void> {\n if (this.watcher) {\n await this.watcher.close();\n }\n\n if (this.sseServer) {\n this.sseServer.close();\n }\n\n logger.info('Claude monitor stopped');\n }\n\n /**\n * Log an activity\n */\n async logActivity(type: string, data: any): Promise<void> {\n const activity: ClaudeActivity = {\n id: uuidv4(),\n timestamp: new Date().toISOString(),\n type,\n data,\n source: 'api'\n };\n\n // Write to log file\n const logFile = path.join(this.logsDir, 'activity.log');\n const logLine = `[${activity.timestamp}] ${type}: ${JSON.stringify(data)}\\n`;\n await fs.appendFile(logFile, logLine);\n\n // Emit event\n this.emitActivity(activity);\n }\n\n /**\n * Start file watcher\n */\n private startFileWatcher(): void {\n this.watcher = watch(this.claudeDir, {\n persistent: true,\n ignoreInitial: true,\n depth: 2\n });\n\n // Watch for task file changes\n this.watcher.on('change', async (filePath) => {\n if (filePath.includes('current-task.md')) {\n const content = await fs.readFile(filePath, 'utf-8');\n this.emitActivity({\n id: uuidv4(),\n timestamp: new Date().toISOString(),\n type: 'task-update',\n data: { file: filePath, content },\n source: 'file'\n });\n }\n });\n\n // Watch for new log entries\n this.watcher.on('add', async (filePath) => {\n if (filePath.includes('logs/')) {\n await this.parseLogFile(filePath);\n }\n });\n\n // Watch for log updates\n this.watcher.on('change', async (filePath) => {\n if (filePath.includes('logs/')) {\n await this.parseLogFile(filePath, true);\n }\n });\n }\n\n /**\n * Parse existing log files\n */\n private async parseExistingLogs(): Promise<void> {\n const logFiles = await fs.readdir(this.logsDir).catch(() => []);\n \n for (const file of logFiles) {\n if (file.endsWith('.log')) {\n await this.parseLogFile(path.join(this.logsDir, file));\n }\n }\n }\n\n /**\n * Parse a log file\n */\n private async parseLogFile(filePath: string, tailOnly = false): Promise<void> {\n try {\n const content = await fs.readFile(filePath, 'utf-8');\n const lines = content.split('\\n').filter(line => line.trim());\n\n // If tailing, only process last few lines\n const linesToProcess = tailOnly ? lines.slice(-10) : lines;\n\n for (const line of linesToProcess) {\n const activity = this.parseLogLine(line);\n if (activity) {\n this.emitActivity(activity);\n }\n }\n } catch (error) {\n logger.error(`Failed to parse log file ${filePath}:`, error);\n }\n }\n\n /**\n * Parse a single log line\n */\n private parseLogLine(line: string): ClaudeActivity | null {\n // Parse different log formats\n const patterns = [\n // Standard activity log\n /^\\[(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2})\\] (\\w+): (.+)$/,\n // Tool usage logs\n /^\\[(.+?)\\] (PRE_TOOL|POST_TOOL): (\\w+) \\| (.+)$/,\n // Session logs\n /^\\[(.+?)\\] (SESSION_START|SESSION_END)$/\n ];\n\n for (const pattern of patterns) {\n const match = line.match(pattern);\n if (match) {\n return {\n id: uuidv4(),\n timestamp: match[1],\n type: match[2].toLowerCase().replace(/_/g, '-'),\n data: match[3] ? this.parseLogData(match[3]) : {},\n source: 'hook'\n };\n }\n }\n\n return null;\n }\n\n /**\n * Parse log data\n */\n private parseLogData(data: string): any {\n try {\n return JSON.parse(data);\n } catch {\n // Try to parse key-value pairs\n const pairs = data.split(' | ');\n const result: any = {};\n \n for (const pair of pairs) {\n const [key, value] = pair.split(': ');\n if (key && value) {\n result[key.toLowerCase()] = value;\n }\n }\n \n return Object.keys(result).length > 0 ? result : data;\n }\n }\n\n /**\n * Emit activity to appropriate output\n */\n private emitActivity(activity: ClaudeActivity): void {\n // Add to buffer\n this.activityBuffer.push(activity);\n if (this.activityBuffer.length > this.maxBufferSize) {\n this.activityBuffer.shift();\n }\n\n // Emit event\n this.emit('activity', activity);\n\n // Handle different output formats\n switch (this.options.outputFormat) {\n case 'json':\n console.log(JSON.stringify(activity));\n break;\n case 'text':\n console.log(`[${activity.timestamp}] ${activity.type}: ${JSON.stringify(activity.data)}`);\n break;\n case 'sse':\n this.broadcastSSE(activity);\n break;\n }\n\n // Check for specific events\n if (activity.type === 'post-tool' && activity.data.tool === 'TodoWrite') {\n this.emit('task-completed', activity);\n }\n }\n\n /**\n * Start SSE server\n */\n private async startSSEServer(): Promise<void> {\n const app = express();\n const port = this.options.ssePort || 3002;\n\n // SSE endpoint\n app.get('/claude/events', (req, res) => {\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n 'Access-Control-Allow-Origin': '*'\n });\n\n // Send initial data\n res.write(`data: ${JSON.stringify({ type: 'connected' })}\\n\\n`);\n\n // Send buffered activities\n for (const activity of this.activityBuffer) {\n res.write(`data: ${JSON.stringify(activity)}\\n\\n`);\n }\n\n // Add to clients\n this.sseClients.add(res);\n\n // Remove on close\n req.on('close', () => {\n this.sseClients.delete(res);\n });\n });\n\n // Activity summary endpoint\n app.get('/claude/summary', (req, res) => {\n const summary = this.getActivitySummary();\n res.json(summary);\n });\n\n this.sseServer = app.listen(port);\n logger.info(`SSE server started on port ${port}`);\n }\n\n /**\n * Broadcast to SSE clients\n */\n private broadcastSSE(activity: ClaudeActivity): void {\n for (const client of this.sseClients) {\n client.write(`data: ${JSON.stringify(activity)}\\n\\n`);\n }\n }\n\n /**\n * Get activity summary\n */\n private getActivitySummary(): any {\n const summary = {\n totalActivities: this.activityBuffer.length,\n recentActivities: this.activityBuffer.slice(-10),\n activityTypes: {} as Record<string, number>,\n toolUsage: {} as Record<string, number>\n };\n\n for (const activity of this.activityBuffer) {\n // Count by type\n summary.activityTypes[activity.type] = (summary.activityTypes[activity.type] || 0) + 1;\n\n // Count tool usage\n if (activity.type === 'post-tool' && activity.data.tool) {\n summary.toolUsage[activity.data.tool] = (summary.toolUsage[activity.data.tool] || 0) + 1;\n }\n }\n\n return summary;\n }\n}","import { spawn, ChildProcess } from 'child_process';\nimport { EventEmitter } from 'events';\nimport * as pty from 'node-pty';\nimport * as fs from 'fs-extra';\nimport * as path from 'path';\nimport { logger } from '../utils/logger';\nimport { ClaudeProcessOptions, ClaudeOutput, ProcessInfo } from '../types';\nimport { ClaudeOutputParser } from '../utils/output-parser';\nimport stripAnsi from 'strip-ansi';\n\n// 兼容性处理\nconst stripAnsiCompat = typeof stripAnsi === 'function' ? stripAnsi : (stripAnsi as any).default || ((str: string) => str);\n\n\nexport class ClaudeProcessManager extends EventEmitter {\n private process?: ChildProcess | any; // pty.IPty\n private projectRoot: string;\n private outputBuffer: ClaudeOutput[] = [];\n private maxBufferSize = 10000;\n private isRunning = false;\n private outputParser: ClaudeOutputParser;\n\n constructor(private options: ClaudeProcessOptions = {}) {\n super();\n this.projectRoot = options.projectRoot || process.cwd();\n this.outputParser = new ClaudeOutputParser();\n }\n\n /**\n * Start Claude Code with enhanced process control\n */\n async start(args: string[] = []): Promise<void> {\n if (this.isRunning) {\n throw new Error('Claude Code is already running');\n }\n\n const env = {\n ...process.env,\n GONGFU_INTEGRATION: '1',\n FORCE_COLOR: '0', // Disable color for easier parsing\n ...this.options.env\n };\n\n if (this.options.usePty) {\n // Use pseudo-terminal for full control\n this.startWithPty(args, env);\n } else {\n // Use standard spawn\n this.startWithSpawn(args, env);\n }\n\n this.isRunning = true;\n this.emit('started');\n }\n\n /**\n * Start with node-pty for better terminal emulation\n */\n private startWithPty(args: string[], env: any): void {\n const pty = require('node-pty');\n \n this.process = pty.spawn('claude', ['code', ...args], {\n name: 'xterm-color',\n cols: 120,\n rows: 30,\n cwd: this.projectRoot,\n env\n });\n\n this.process.onData((data: string) => {\n this.handleOutput(data, 'stdout');\n });\n\n this.process.onExit((exitCode: number) => {\n this.handleExit(exitCode);\n });\n }\n\n /**\n * Start with standard spawn\n */\n private startWithSpawn(args: string[], env: any): void {\n this.process = spawn('claude', ['code', ...args], {\n cwd: this.projectRoot,\n env,\n stdio: this.options.captureOutput ? 'pipe' : 'inherit'\n });\n\n if (this.options.captureOutput && this.process.stdout && this.process.stderr) {\n this.process.stdout.on('data', (data: Buffer) => {\n this.handleOutput(data.toString(), 'stdout');\n });\n\n this.process.stderr.on('data', (data: Buffer) => {\n this.handleOutput(data.toString(), 'stderr');\n });\n }\n\n this.process.on('exit', (code: number | null) => {\n this.handleExit(code || 0);\n });\n\n this.process.on('error', (error: Error) => {\n this.emit('error', error);\n });\n }\n\n /**\n * Handle output from Claude Code\n */\n private handleOutput(data: string, type: 'stdout' | 'stderr'): void {\n const output: ClaudeOutput = {\n timestamp: new Date().toISOString(),\n type,\n content: data\n };\n\n // Parse the output\n const parsed = this.outputParser.parse(data);\n if (parsed) {\n output.parsed = parsed;\n this.emit('parsed-output', parsed);\n }\n\n // Add to buffer\n this.outputBuffer.push(output);\n if (this.outputBuffer.length > this.maxBufferSize) {\n this.outputBuffer.shift();\n }\n\n // Emit raw output\n this.emit('output', output);\n\n // Log if needed\n if (this.options.captureOutput) {\n this.logOutput(output);\n }\n }\n\n /**\n * Handle process exit\n */\n private handleExit(code: number): void {\n this.isRunning = false;\n this.process = undefined;\n this.emit('exit', code);\n logger.info(`Claude Code exited with code ${code}`);\n }\n\n /**\n * Send input to Claude Code\n */\n async sendInput(input: string): Promise<void> {\n if (!this.process) {\n throw new Error('Claude Code is not running');\n }\n\n if (this.options.usePty) {\n this.process.write(input);\n } else if (this.process.stdin) {\n this.process.stdin.write(input);\n }\n\n this.emit('input', input);\n }\n\n /**\n * Send a command (with newline)\n */\n async sendCommand(command: string): Promise<void> {\n await this.sendInput(`${command}\\n`);\n }\n\n /**\n * Stop Claude Code\n */\n async stop(): Promise<void> {\n if (!this.process) {\n return;\n }\n\n // Try graceful shutdown first\n if (this.options.usePty) {\n this.process.write('\\x03'); // Ctrl+C\n await this.waitForExit(5000);\n }\n\n // Force kill if still running\n if (this.process) {\n if (this.options.usePty) {\n this.process.kill();\n } else {\n this.process.kill('SIGTERM');\n }\n }\n }\n\n /**\n * Wait for process to exit\n */\n private waitForExit(timeout: number): Promise<void> {\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, timeout);\n this.once('exit', () => {\n clearTimeout(timer);\n resolve();\n });\n });\n }\n\n /**\n * Get recent output\n */\n getRecentOutput(lines: number = 100): ClaudeOutput[] {\n return this.outputBuffer.slice(-lines);\n }\n\n /**\n * Search output history\n */\n searchOutput(pattern: string | RegExp): ClaudeOutput[] {\n const regex = typeof pattern === 'string' ? new RegExp(pattern, 'i') : pattern;\n return this.outputBuffer.filter(output => \n regex.test(output.content) || \n (output.parsed && regex.test(JSON.stringify(output.parsed)))\n );\n }\n\n /**\n * Log output to file\n */\n private async logOutput(output: ClaudeOutput): Promise<void> {\n const logDir = path.join(this.projectRoot, '.claude', 'logs');\n await fs.ensureDir(logDir);\n\n const logFile = path.join(logDir, `claude-${new Date().toISOString().split('T')[0]}.log`);\n const logLine = `[${output.timestamp}] [${output.type}] ${stripAnsi(output.content)}`;\n \n await fs.appendFile(logFile, logLine);\n }\n\n /**\n * Get process info\n */\n getInfo(): ProcessInfo {\n return {\n isRunning: this.isRunning,\n pid: this.process?.pid,\n uptime: this.process ? Date.now() - this.process.startTime : 0,\n bufferSize: this.outputBuffer.length\n };\n }\n}","import { ParsedOutput } from '../types';\n\n// Handle both ESM and CJS imports for strip-ansi\nlet stripAnsi: any;\ntry {\n stripAnsi = require('strip-ansi');\n if (stripAnsi.default) stripAnsi = stripAnsi.default;\n} catch {\n // Fallback: simple ANSI removal\n stripAnsi = (str: string) => str.replace(/\\u001b\\[[0-9;]*m/g, '');\n}\n\n/**\n * Parser for Claude Code output\n */\nexport class ClaudeOutputParser {\n private patterns = {\n toolUse: /(?:Running|Executing|Using tool):\\s*(\\w+)(?:\\s+with\\s+(.+))?/,\n fileChange: /(?:Modified|Created|Deleted):\\s*(.+)/,\n thinking: /(?:Thinking|Planning|Analyzing):\\s*(.+)/,\n error: /(?:Error|Failed):\\s*(.+)/,\n task: /(?:Task|Working on):\\s*(.+)/,\n progress: /(?:Progress|Completed):\\s*(\\d+)%?\\s*(?:of\\s+)?(.+)?/,\n command: /(?:Command|Executing command):\\s*(.+)/,\n codeBlock: /```(\\w+)?\\n([\\s\\S]*?)```/,\n response: /(?:Response|Output):\\s*(.+)/\n };\n\n parse(output: string): ParsedOutput | null {\n const cleaned = stripAnsi(output).trim();\n if (!cleaned) return null;\n\n // Try each pattern\n for (const [type, pattern] of Object.entries(this.patterns)) {\n const match = cleaned.match(pattern);\n if (match) {\n return {\n type,\n raw: cleaned,\n ...this.extractMatchData(type, match)\n };\n }\n }\n\n // Check for JSON output\n if (cleaned.startsWith('{') || cleaned.startsWith('[')) {\n try {\n return {\n type: 'json',\n raw: cleaned,\n data: JSON.parse(cleaned)\n };\n } catch {\n // Not valid JSON\n }\n }\n\n // Check for structured output patterns\n if (cleaned.includes('|') && cleaned.split('|').length > 2) {\n return this.parseTableRow(cleaned);\n }\n\n // Default to text\n return {\n type: 'text',\n raw: cleaned,\n content: cleaned\n };\n }\n\n private extractMatchData(type: string, match: RegExpMatchArray): any {\n switch (type) {\n case 'toolUse':\n return {\n tool: match[1],\n args: match[2]\n };\n case 'fileChange':\n return {\n file: match[1]\n };\n case 'thinking':\n case 'error':\n case 'task':\n case 'command':\n case 'response':\n return {\n message: match[1]\n };\n case 'progress':\n return {\n percent: parseInt(match[1]),\n task: match[2]\n };\n case 'codeBlock':\n return {\n language: match[1] || 'text',\n code: match[2]\n };\n default:\n return {};\n }\n }\n\n private parseTableRow(line: string): ParsedOutput {\n const parts = line.split('|').map(p => p.trim()).filter(p => p);\n return {\n type: 'table-row',\n raw: line,\n columns: parts\n };\n }\n\n /**\n * Parse multiple lines of output\n */\n parseLines(output: string): ParsedOutput[] {\n const lines = output.split('\\n');\n const results: ParsedOutput[] = [];\n\n for (const line of lines) {\n const parsed = this.parse(line);\n if (parsed) {\n results.push(parsed);\n }\n }\n\n return results;\n }\n\n /**\n * Extract code blocks from output\n */\n extractCodeBlocks(output: string): Array<{ language: string; code: string }> {\n const blocks: Array<{ language: string; code: string }> = [];\n const matches = output.matchAll(/```(\\w+)?\\n([\\s\\S]*?)```/g);\n\n for (const match of matches) {\n blocks.push({\n language: match[1] || 'text',\n code: match[2]\n });\n }\n\n return blocks;\n }\n\n /**\n * Extract tool calls from output\n */\n extractToolCalls(output: string): Array<{ tool: string; args?: string }> {\n const calls: Array<{ tool: string; args?: string }> = [];\n const lines = output.split('\\n');\n\n for (const line of lines) {\n const match = line.match(this.patterns.toolUse);\n if (match) {\n calls.push({\n tool: match[1],\n args: match[2]\n });\n }\n }\n\n return calls;\n }\n}","import { EventEmitter } from 'events';\nimport express from 'express';\nimport { watch, FSWatcher } from 'chokidar';\nimport * as fs from 'fs-extra';\nimport * as path from 'path';\nimport { logger } from '../utils/logger';\nimport { ClaudeActivity } from '../types';\n\n\nexport class ClaudeActivityHandler extends EventEmitter {\n private projectRoot: string;\n private claudeDir: string;\n private watcher?: FSWatcher;\n private activities: ClaudeActivity[] = [];\n private maxActivities = 1000;\n\n constructor(projectRoot: string) {\n super();\n this.projectRoot = projectRoot;\n this.claudeDir = path.join(projectRoot, '.claude');\n }\n\n async start(): Promise<void> {\n // Ensure directories exist\n await fs.ensureDir(path.join(this.claudeDir, 'logs'));\n\n // Start file watcher\n this.startWatcher();\n\n // Parse existing logs\n await this.parseExistingLogs();\n\n logger.info('Claude activity handler started');\n }\n\n async stop(): Promise<void> {\n if (this.watcher) {\n await this.watcher.close();\n }\n logger.info('Claude activity handler stopped');\n }\n\n /**\n * Add SSE endpoints to express app\n */\n setupSSEEndpoints(app: express.Application): void {\n // Claude activity stream endpoint\n app.get('/mcp/claude/stream', (req, res) => {\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n 'Access-Control-Allow-Origin': '*'\n });\n\n // Send initial data\n res.write(`event: connected\\n`);\n res.write(`data: ${JSON.stringify({ \n type: 'connected',\n activities: this.activities.slice(-10)\n })}\\n\\n`);\n\n // Listen for new activities\n const activityHandler = (activity: ClaudeActivity) => {\n res.write(`event: activity\\n`);\n res.write(`data: ${JSON.stringify(activity)}\\n\\n`);\n };\n\n this.on('activity', activityHandler);\n\n // Keep alive\n const keepAlive = setInterval(() => {\n res.write(':keepalive\\n\\n');\n }, 30000);\n\n // Cleanup on disconnect\n req.on('close', () => {\n this.off('activity', activityHandler);\n clearInterval(keepAlive);\n });\n });\n\n // Activity summary endpoint\n app.get('/mcp/claude/summary', (req, res) => {\n const summary = this.getActivitySummary();\n res.json(summary);\n });\n\n // Current task endpoint\n app.get('/mcp/claude/current-task', async (req, res) => {\n const task = await this.getCurrentTask();\n res.json(task);\n });\n\n // Log activity endpoint\n app.post('/mcp/claude/log', express.json(), (req, res) => {\n const { type, data } = req.body;\n this.logActivity(type, data);\n res.json({ success: true });\n });\n }\n\n /**\n * Start file watcher\n */\n private startWatcher(): void {\n this.watcher = watch(this.claudeDir, {\n persistent: true,\n ignoreInitial: true,\n depth: 3\n });\n\n // Watch for log file changes\n this.watcher.on('change', async (filePath) => {\n if (filePath.includes('logs/')) {\n await this.parseLogFile(filePath, true);\n } else if (filePath.includes('current-task.md')) {\n const content = await fs.readFile(filePath, 'utf-8');\n this.addActivity({\n type: 'task-update',\n data: { file: filePath, content },\n source: 'file'\n });\n }\n });\n\n // Watch for new files\n this.watcher.on('add', async (filePath) => {\n if (filePath.includes('logs/') && filePath.endsWith('.log')) {\n await this.parseLogFile(filePath);\n }\n });\n }\n\n /**\n * Parse existing log files\n */\n private async parseExistingLogs(): Promise<void> {\n const logsDir = path.join(this.claudeDir, 'logs');\n \n try {\n const files = await fs.readdir(logsDir);\n for (const file of files) {\n if (file.endsWith('.log')) {\n await this.parseLogFile(path.join(logsDir, file));\n }\n }\n } catch (error) {\n logger.debug('No existing logs to parse');\n }\n }\n\n /**\n * Parse a log file\n */\n private async parseLogFile(filePath: string, tailOnly = false): Promise<void> {\n try {\n const content = await fs.readFile(filePath, 'utf-8');\n const lines = content.split('\\n').filter(line => line.trim());\n \n // If tailing, only process last few lines\n const linesToProcess = tailOnly ? lines.slice(-20) : lines;\n\n for (const line of linesToProcess) {\n const activity = this.parseLogLine(line);\n if (activity) {\n this.addActivity(activity);\n }\n }\n } catch (error) {\n logger.error(`Failed to parse log file ${filePath}:`, error);\n }\n }\n\n /**\n * Parse a log line\n */\n private parseLogLine(line: string): Omit<ClaudeActivity, 'id' | 'timestamp'> | null {\n // Tool usage patterns\n const toolMatch = line.match(/^\\[(.+?)\\] (PRE_TOOL|POST_TOOL|TOOL_START|TOOL_END): (\\w+) \\| (.+)$/);\n if (toolMatch) {\n return {\n type: 'tool-use',\n data: {\n phase: toolMatch[2],\n tool: toolMatch[3],\n details: this.parseDetails(toolMatch[4])\n },\n source: 'hook'\n };\n }\n\n // File change patterns\n const fileMatch = line.match(/^\\[(.+?)\\] FILE_MODIFIED: (.+)$/);\n if (fileMatch) {\n return {\n type: 'file-change',\n data: { file: fileMatch[2] },\n source: 'hook'\n };\n }\n\n // Command patterns\n const cmdMatch = line.match(/^\\[(.+?)\\] COMMAND_RUN: (.+)$/);\n if (cmdMatch) {\n return {\n type: 'command',\n data: { command: cmdMatch[2] },\n source: 'hook'\n };\n }\n\n // Session patterns\n const sessionMatch = line.match(/^\\[(.+?)\\] (SESSION_START|SESSION_END)$/);\n if (sessionMatch) {\n return {\n type: 'session',\n data: { event: sessionMatch[2] },\n source: 'hook'\n };\n }\n\n return null;\n }\n\n /**\n * Parse details from log line\n */\n private parseDetails(details: string): any {\n try {\n return JSON.parse(details);\n } catch {\n // Parse key-value pairs\n const pairs = details.split(' | ');\n const result: any = {};\n \n for (const pair of pairs) {\n const [key, value] = pair.split(': ');\n if (key && value) {\n result[key.toLowerCase().replace(/\\s+/g, '_')] = value;\n }\n }\n \n return Object.keys(result).length > 0 ? result : details;\n }\n }\n\n /**\n * Add activity to the list\n */\n private addActivity(activity: Omit<ClaudeActivity, 'id' | 'timestamp'>): void {\n const fullActivity: ClaudeActivity = {\n id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,\n timestamp: new Date().toISOString(),\n ...activity\n };\n\n this.activities.push(fullActivity);\n \n // Maintain size limit\n if (this.activities.length > this.maxActivities) {\n this.activities = this.activities.slice(-this.maxActivities);\n }\n\n // Emit event\n this.emit('activity', fullActivity);\n }\n\n /**\n * Log an activity manually\n */\n logActivity(type: ClaudeActivity['type'], data: any): void {\n this.addActivity({\n type,\n data,\n source: 'monitor'\n });\n }\n\n /**\n * Get activity summary\n */\n getActivitySummary(): any {\n const summary = {\n totalActivities: this.activities.length,\n recentActivities: this.activities.slice(-20),\n byType: {} as Record<string, number>,\n bySource: {} as Record<string, number>,\n toolUsage: {} as Record<string, number>,\n fileChanges: [] as string[]\n };\n\n for (const activity of this.activities) {\n // Count by type\n summary.byType[activity.type] = (summary.byType[activity.type] || 0) + 1;\n \n // Count by source\n summary.bySource[activity.source] = (summary.bySource[activity.source] || 0) + 1;\n\n // Track tool usage\n if (activity.type === 'tool-use' && activity.data.tool) {\n summary.toolUsage[activity.data.tool] = (summary.toolUsage[activity.data.tool] || 0) + 1;\n }\n\n // Track file changes\n if (activity.type === 'file-change' && activity.data.file) {\n if (!summary.fileChanges.includes(activity.data.file)) {\n summary.fileChanges.push(activity.data.file);\n }\n }\n }\n\n return summary;\n }\n\n /**\n * Get current task\n */\n async getCurrentTask(): Promise<any> {\n const taskFile = path.join(this.claudeDir, 'current-task.md');\n \n try {\n const content = await fs.readFile(taskFile, 'utf-8');\n const idMatch = content.match(/## Task ID: (.+)/);\n const descMatch = content.match(/## Description\\n(.+)/);\n const priorityMatch = content.match(/## Priority: (.+)/);\n const startedMatch = content.match(/- Started: (.+)/);\n\n return {\n exists: true,\n id: idMatch?.[1],\n description: descMatch?.[1],\n priority: priorityMatch?.[1],\n startedAt: startedMatch?.[1],\n content\n };\n } catch {\n return { exists: false };\n }\n }\n}"],"mappings":"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