UNPKG

@gongfu/claude-integration

Version:

Claude Code integration toolkit for Gongfu workflow system

1,206 lines (1,184 loc) 33.2 kB
/** * @gongfu/claude-integration * Claude Code integration toolkit for Gongfu workflow system * * Copyright (c) 2025 Foundation * Licensed under MIT */ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) { if (typeof require !== "undefined") return require.apply(this, arguments); throw Error('Dynamic require of "' + x + '" is not supported'); }); // src/core/claude-controller.ts import * as fs from "fs-extra"; import * as path from "path"; import { spawn } from "child_process"; import { EventEmitter } from "events"; // src/utils/logger.ts import pc from "picocolors"; function createLogger(prefix) { const p = prefix ? `[${prefix}] ` : ""; return { info: (message, ...args) => { console.log(pc.blue(p + message), ...args); }, warn: (message, ...args) => { console.warn(pc.yellow(p + message), ...args); }, error: (message, ...args) => { console.error(pc.red(p + message), ...args); }, debug: (message, ...args) => { if (process.env.DEBUG) { console.log(pc.gray(p + message), ...args); } } }; } var logger = createLogger("claude-integration"); // src/core/claude-controller.ts import { v4 as uuidv4 } from "uuid"; var ClaudeController = class extends EventEmitter { constructor(options = {}) { super(); this.options = options; this.projectRoot = options.projectRoot || process.cwd(); this.claudeDir = path.join(this.projectRoot, ".claude"); this.currentTaskFile = path.join(this.claudeDir, "current-task.md"); } projectRoot; claudeDir; currentTaskFile; process; outputBuffer = ""; /** * Assign a task to Claude Code */ async assignTask(taskDescription, options = {}) { await fs.ensureDir(this.claudeDir); const taskId = uuidv4(); const task = { id: taskId, content: taskDescription, priority: options.priority || "medium", status: "in_progress", createdAt: /* @__PURE__ */ new Date(), updatedAt: /* @__PURE__ */ new Date() }; const taskContent = this.createTaskInstruction(task); await fs.writeFile(this.currentTaskFile, taskContent); const signalFile = path.join(this.claudeDir, "new-task.signal"); await fs.writeFile(signalFile, JSON.stringify({ taskId, timestamp: (/* @__PURE__ */ new Date()).toISOString() })); setTimeout(() => { fs.remove(signalFile).catch(() => { }); }, 5e3); logger.info(`Task assigned: ${taskId}`); this.emit("task-assigned", { taskId, task }); return taskId; } /** * Create task instruction content */ createTaskInstruction(task) { return `# \u{1F3AF} Current Task ## Task ID: ${task.id} ## Description ${task.content} ## Priority: ${task.priority} ## Instructions Please complete this task following these guidelines: 1. **Understand**: Analyze the requirements carefully 2. **Plan**: Create a step-by-step approach 3. **Implement**: Write clean, maintainable code 4. **Test**: Ensure the solution works correctly 5. **Document**: Update relevant documentation ## Completion When you finish this task: 1. Run tests to ensure everything works 2. Update the task status using the todo tool 3. Commit your changes with a descriptive message ## Context - Project: ${path.basename(this.projectRoot)} - Started: ${(/* @__PURE__ */ new Date()).toISOString()} - Workflow integration enabled --- *This task was assigned by the Gongfu workflow system* `; } /** * Start Claude Code in background mode */ async startInBackground() { if (this.process) { throw new Error("Claude Code is already running"); } const wrapperScript = path.join(this.claudeDir, "claude-wrapper.sh"); const wrapperContent = `#!/bin/bash cd "${this.projectRoot}" export GONGFU_TASK_MODE=1 claude code 2>&1 | tee -a "${path.join(this.claudeDir, "logs", "claude-output.log")}" `; await fs.writeFile(wrapperScript, wrapperContent); await fs.chmod(wrapperScript, "755"); this.process = spawn("bash", [wrapperScript], { detached: true, stdio: ["ignore", "pipe", "pipe"], cwd: this.projectRoot }); if (this.options.logOutput) { this.process.stdout?.on("data", (data) => { this.outputBuffer += data.toString(); this.parseOutput(data.toString()); }); this.process.stderr?.on("data", (data) => { logger.error("Claude Code error:", data.toString()); }); } this.process.on("exit", (code) => { logger.info(`Claude Code exited with code ${code}`); this.process = void 0; this.emit("process-exit", code); }); this.process.unref(); logger.info("Claude Code started in background"); } /** * Parse Claude Code output */ parseOutput(output) { const lines = output.split("\n"); for (const line of lines) { if (line.includes("Running:") || line.includes("Executing:")) { this.emit("tool-use", { timestamp: (/* @__PURE__ */ new Date()).toISOString(), command: line }); } if (line.includes("Modified:") || line.includes("Created:")) { this.emit("file-change", { timestamp: (/* @__PURE__ */ new Date()).toISOString(), file: line }); } if (line.includes("Thinking:") || line.includes("Planning:")) { this.emit("thinking", { timestamp: (/* @__PURE__ */ new Date()).toISOString(), thought: line }); } } } /** * Get Claude Code status */ async getStatus() { const status = { isActive: false, recentActivity: [] }; if (this.process && !this.process.killed) { status.isActive = true; status.processId = this.process.pid; } if (await fs.pathExists(this.currentTaskFile)) { const content = await fs.readFile(this.currentTaskFile, "utf-8"); const idMatch = content.match(/## Task ID: (.+)/); const descMatch = content.match(/## Description\n(.+)/); const startMatch = content.match(/- Started: (.+)/); if (idMatch && descMatch) { status.currentTask = { id: idMatch[1], content: descMatch[1], startedAt: startMatch ? startMatch[1] : (/* @__PURE__ */ new Date()).toISOString() }; } } const activityLog = path.join(this.claudeDir, "logs", "activity.log"); if (await fs.pathExists(activityLog)) { const content = await fs.readFile(activityLog, "utf-8"); const lines = content.split("\n").filter((line) => line.trim()).slice(-10); for (const line of lines) { const match = line.match(/^\[(.+?)\] (\w+): (.+)$/); if (match) { status.recentActivity.push({ timestamp: match[1], type: match[2], description: match[3] }); } } } return status; } /** * Setup enhanced hooks for Claude Code */ async setupHooks(enhanced = false) { const settingsPath = path.join(this.claudeDir, "settings.local.json"); let settings = {}; if (await fs.pathExists(settingsPath)) { settings = await fs.readJson(settingsPath); } const logsDir = path.join(this.claudeDir, "logs"); await fs.ensureDir(logsDir); if (enhanced) { settings.hooks = { ...settings.hooks, PreToolUse: [ { matcher: ".*", hooks: [{ type: "command", command: `echo "[$(date '+%Y-%m-%d %H:%M:%S')] TOOL_START: {tool} | {args}" >> "${path.join(logsDir, "activity.log")}"`, timeout: 1 }] } ], PostToolUse: [ ...settings.hooks?.PostToolUse || [], { matcher: ".*", hooks: [{ type: "command", command: `echo "[$(date '+%Y-%m-%d %H:%M:%S')] TOOL_END: {tool} | Success: {success}" >> "${path.join(logsDir, "activity.log")}"`, timeout: 1 }] }, { matcher: "^(Edit|Write|MultiEdit)", hooks: [{ type: "command", command: `cd "${this.projectRoot}" && npx @gongfu/workflow-cli claude log --type file-change --data "{file_path}"`, timeout: 2 }] } ], Start: [{ hooks: [{ type: "command", command: `cd "${this.projectRoot}" && npx @gongfu/workflow-cli claude log --type session-start`, timeout: 1 }] }], Stop: [{ hooks: [{ type: "command", command: `cd "${this.projectRoot}" && npx @gongfu/workflow-cli claude log --type session-end`, timeout: 1 }] }] }; } await fs.writeJson(settingsPath, settings, { spaces: 2 }); logger.info("Claude Code hooks configured"); } /** * Stop Claude Code */ async stop() { if (this.process && !this.process.killed) { this.process.kill("SIGTERM"); logger.info("Claude Code stopped"); } } }; // src/core/claude-monitor.ts import { EventEmitter as EventEmitter2 } from "events"; import * as fs2 from "fs-extra"; import * as path2 from "path"; import { watch } from "chokidar"; import express from "express"; import { v4 as uuidv42 } from "uuid"; var ClaudeMonitor = class extends EventEmitter2 { constructor(options = {}) { super(); this.options = options; this.projectRoot = options.projectRoot || process.cwd(); this.claudeDir = path2.join(this.projectRoot, ".claude"); this.logsDir = path2.join(this.claudeDir, "logs"); } projectRoot; claudeDir; logsDir; watcher; sseServer; sseClients = /* @__PURE__ */ new Set(); activityBuffer = []; maxBufferSize = 1e3; /** * Start monitoring Claude Code activity */ async start() { await fs2.ensureDir(this.logsDir); this.startFileWatcher(); if (this.options.outputFormat === "sse") { await this.startSSEServer(); } await this.parseExistingLogs(); logger.info("Claude monitor started"); } /** * Stop monitoring */ async stop() { if (this.watcher) { await this.watcher.close(); } if (this.sseServer) { this.sseServer.close(); } logger.info("Claude monitor stopped"); } /** * Log an activity */ async logActivity(type, data) { const activity = { id: uuidv42(), timestamp: (/* @__PURE__ */ new Date()).toISOString(), type, data, source: "api" }; const logFile = path2.join(this.logsDir, "activity.log"); const logLine = `[${activity.timestamp}] ${type}: ${JSON.stringify(data)} `; await fs2.appendFile(logFile, logLine); this.emitActivity(activity); } /** * Start file watcher */ startFileWatcher() { this.watcher = watch(this.claudeDir, { persistent: true, ignoreInitial: true, depth: 2 }); this.watcher.on("change", async (filePath) => { if (filePath.includes("current-task.md")) { const content = await fs2.readFile(filePath, "utf-8"); this.emitActivity({ id: uuidv42(), timestamp: (/* @__PURE__ */ new Date()).toISOString(), type: "task-update", data: { file: filePath, content }, source: "file" }); } }); this.watcher.on("add", async (filePath) => { if (filePath.includes("logs/")) { await this.parseLogFile(filePath); } }); this.watcher.on("change", async (filePath) => { if (filePath.includes("logs/")) { await this.parseLogFile(filePath, true); } }); } /** * Parse existing log files */ async parseExistingLogs() { const logFiles = await fs2.readdir(this.logsDir).catch(() => []); for (const file of logFiles) { if (file.endsWith(".log")) { await this.parseLogFile(path2.join(this.logsDir, file)); } } } /** * Parse a log file */ async parseLogFile(filePath, tailOnly = false) { try { const content = await fs2.readFile(filePath, "utf-8"); const lines = content.split("\n").filter((line) => line.trim()); const linesToProcess = tailOnly ? lines.slice(-10) : lines; for (const line of linesToProcess) { const activity = this.parseLogLine(line); if (activity) { this.emitActivity(activity); } } } catch (error) { logger.error(`Failed to parse log file ${filePath}:`, error); } } /** * Parse a single log line */ parseLogLine(line) { const patterns = [ // Standard activity log /^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] (\w+): (.+)$/, // Tool usage logs /^\[(.+?)\] (PRE_TOOL|POST_TOOL): (\w+) \| (.+)$/, // Session logs /^\[(.+?)\] (SESSION_START|SESSION_END)$/ ]; for (const pattern of patterns) { const match = line.match(pattern); if (match) { return { id: uuidv42(), timestamp: match[1], type: match[2].toLowerCase().replace(/_/g, "-"), data: match[3] ? this.parseLogData(match[3]) : {}, source: "hook" }; } } return null; } /** * Parse log data */ parseLogData(data) { try { return JSON.parse(data); } catch { const pairs = data.split(" | "); const result = {}; for (const pair of pairs) { const [key, value] = pair.split(": "); if (key && value) { result[key.toLowerCase()] = value; } } return Object.keys(result).length > 0 ? result : data; } } /** * Emit activity to appropriate output */ emitActivity(activity) { this.activityBuffer.push(activity); if (this.activityBuffer.length > this.maxBufferSize) { this.activityBuffer.shift(); } this.emit("activity", activity); switch (this.options.outputFormat) { case "json": console.log(JSON.stringify(activity)); break; case "text": console.log(`[${activity.timestamp}] ${activity.type}: ${JSON.stringify(activity.data)}`); break; case "sse": this.broadcastSSE(activity); break; } if (activity.type === "post-tool" && activity.data.tool === "TodoWrite") { this.emit("task-completed", activity); } } /** * Start SSE server */ async startSSEServer() { const app = express(); const port = this.options.ssePort || 3002; app.get("/claude/events", (req, res) => { res.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", "Connection": "keep-alive", "Access-Control-Allow-Origin": "*" }); res.write(`data: ${JSON.stringify({ type: "connected" })} `); for (const activity of this.activityBuffer) { res.write(`data: ${JSON.stringify(activity)} `); } this.sseClients.add(res); req.on("close", () => { this.sseClients.delete(res); }); }); app.get("/claude/summary", (req, res) => { const summary = this.getActivitySummary(); res.json(summary); }); this.sseServer = app.listen(port); logger.info(`SSE server started on port ${port}`); } /** * Broadcast to SSE clients */ broadcastSSE(activity) { for (const client of this.sseClients) { client.write(`data: ${JSON.stringify(activity)} `); } } /** * Get activity summary */ getActivitySummary() { const summary = { totalActivities: this.activityBuffer.length, recentActivities: this.activityBuffer.slice(-10), activityTypes: {}, toolUsage: {} }; for (const activity of this.activityBuffer) { summary.activityTypes[activity.type] = (summary.activityTypes[activity.type] || 0) + 1; if (activity.type === "post-tool" && activity.data.tool) { summary.toolUsage[activity.data.tool] = (summary.toolUsage[activity.data.tool] || 0) + 1; } } return summary; } }; // src/core/claude-process-manager.ts import { spawn as spawn2 } from "child_process"; import { EventEmitter as EventEmitter3 } from "events"; import * as fs3 from "fs-extra"; import * as path3 from "path"; // src/utils/output-parser.ts var stripAnsi; try { stripAnsi = __require("strip-ansi"); if (stripAnsi.default) stripAnsi = stripAnsi.default; } catch { stripAnsi = (str) => str.replace(/\u001b\[[0-9;]*m/g, ""); } var ClaudeOutputParser = class { patterns = { toolUse: /(?:Running|Executing|Using tool):\s*(\w+)(?:\s+with\s+(.+))?/, fileChange: /(?:Modified|Created|Deleted):\s*(.+)/, thinking: /(?:Thinking|Planning|Analyzing):\s*(.+)/, error: /(?:Error|Failed):\s*(.+)/, task: /(?:Task|Working on):\s*(.+)/, progress: /(?:Progress|Completed):\s*(\d+)%?\s*(?:of\s+)?(.+)?/, command: /(?:Command|Executing command):\s*(.+)/, codeBlock: /```(\w+)?\n([\s\S]*?)```/, response: /(?:Response|Output):\s*(.+)/ }; parse(output) { const cleaned = stripAnsi(output).trim(); if (!cleaned) return null; for (const [type, pattern] of Object.entries(this.patterns)) { const match = cleaned.match(pattern); if (match) { return { type, raw: cleaned, ...this.extractMatchData(type, match) }; } } if (cleaned.startsWith("{") || cleaned.startsWith("[")) { try { return { type: "json", raw: cleaned, data: JSON.parse(cleaned) }; } catch { } } if (cleaned.includes("|") && cleaned.split("|").length > 2) { return this.parseTableRow(cleaned); } return { type: "text", raw: cleaned, content: cleaned }; } extractMatchData(type, match) { switch (type) { case "toolUse": return { tool: match[1], args: match[2] }; case "fileChange": return { file: match[1] }; case "thinking": case "error": case "task": case "command": case "response": return { message: match[1] }; case "progress": return { percent: parseInt(match[1]), task: match[2] }; case "codeBlock": return { language: match[1] || "text", code: match[2] }; default: return {}; } } parseTableRow(line) { const parts = line.split("|").map((p) => p.trim()).filter((p) => p); return { type: "table-row", raw: line, columns: parts }; } /** * Parse multiple lines of output */ parseLines(output) { const lines = output.split("\n"); const results = []; for (const line of lines) { const parsed = this.parse(line); if (parsed) { results.push(parsed); } } return results; } /** * Extract code blocks from output */ extractCodeBlocks(output) { const blocks = []; const matches = output.matchAll(/```(\w+)?\n([\s\S]*?)```/g); for (const match of matches) { blocks.push({ language: match[1] || "text", code: match[2] }); } return blocks; } /** * Extract tool calls from output */ extractToolCalls(output) { const calls = []; const lines = output.split("\n"); for (const line of lines) { const match = line.match(this.patterns.toolUse); if (match) { calls.push({ tool: match[1], args: match[2] }); } } return calls; } }; // src/core/claude-process-manager.ts import stripAnsi2 from "strip-ansi"; var stripAnsiCompat = typeof stripAnsi2 === "function" ? stripAnsi2 : stripAnsi2.default || ((str) => str); var ClaudeProcessManager = class extends EventEmitter3 { constructor(options = {}) { super(); this.options = options; this.projectRoot = options.projectRoot || process.cwd(); this.outputParser = new ClaudeOutputParser(); } process; // pty.IPty projectRoot; outputBuffer = []; maxBufferSize = 1e4; isRunning = false; outputParser; /** * Start Claude Code with enhanced process control */ async start(args = []) { if (this.isRunning) { throw new Error("Claude Code is already running"); } const env = { ...process.env, GONGFU_INTEGRATION: "1", FORCE_COLOR: "0", // Disable color for easier parsing ...this.options.env }; if (this.options.usePty) { this.startWithPty(args, env); } else { this.startWithSpawn(args, env); } this.isRunning = true; this.emit("started"); } /** * Start with node-pty for better terminal emulation */ startWithPty(args, env) { const pty = __require("node-pty"); this.process = pty.spawn("claude", ["code", ...args], { name: "xterm-color", cols: 120, rows: 30, cwd: this.projectRoot, env }); this.process.onData((data) => { this.handleOutput(data, "stdout"); }); this.process.onExit((exitCode) => { this.handleExit(exitCode); }); } /** * Start with standard spawn */ startWithSpawn(args, env) { this.process = spawn2("claude", ["code", ...args], { cwd: this.projectRoot, env, stdio: this.options.captureOutput ? "pipe" : "inherit" }); if (this.options.captureOutput && this.process.stdout && this.process.stderr) { this.process.stdout.on("data", (data) => { this.handleOutput(data.toString(), "stdout"); }); this.process.stderr.on("data", (data) => { this.handleOutput(data.toString(), "stderr"); }); } this.process.on("exit", (code) => { this.handleExit(code || 0); }); this.process.on("error", (error) => { this.emit("error", error); }); } /** * Handle output from Claude Code */ handleOutput(data, type) { const output = { timestamp: (/* @__PURE__ */ new Date()).toISOString(), type, content: data }; const parsed = this.outputParser.parse(data); if (parsed) { output.parsed = parsed; this.emit("parsed-output", parsed); } this.outputBuffer.push(output); if (this.outputBuffer.length > this.maxBufferSize) { this.outputBuffer.shift(); } this.emit("output", output); if (this.options.captureOutput) { this.logOutput(output); } } /** * Handle process exit */ handleExit(code) { this.isRunning = false; this.process = void 0; this.emit("exit", code); logger.info(`Claude Code exited with code ${code}`); } /** * Send input to Claude Code */ async sendInput(input) { if (!this.process) { throw new Error("Claude Code is not running"); } if (this.options.usePty) { this.process.write(input); } else if (this.process.stdin) { this.process.stdin.write(input); } this.emit("input", input); } /** * Send a command (with newline) */ async sendCommand(command) { await this.sendInput(`${command} `); } /** * Stop Claude Code */ async stop() { if (!this.process) { return; } if (this.options.usePty) { this.process.write(""); await this.waitForExit(5e3); } if (this.process) { if (this.options.usePty) { this.process.kill(); } else { this.process.kill("SIGTERM"); } } } /** * Wait for process to exit */ waitForExit(timeout) { return new Promise((resolve) => { const timer = setTimeout(resolve, timeout); this.once("exit", () => { clearTimeout(timer); resolve(); }); }); } /** * Get recent output */ getRecentOutput(lines = 100) { return this.outputBuffer.slice(-lines); } /** * Search output history */ searchOutput(pattern) { const regex = typeof pattern === "string" ? new RegExp(pattern, "i") : pattern; return this.outputBuffer.filter( (output) => regex.test(output.content) || output.parsed && regex.test(JSON.stringify(output.parsed)) ); } /** * Log output to file */ async logOutput(output) { const logDir = path3.join(this.projectRoot, ".claude", "logs"); await fs3.ensureDir(logDir); const logFile = path3.join(logDir, `claude-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.log`); const logLine = `[${output.timestamp}] [${output.type}] ${stripAnsi2(output.content)}`; await fs3.appendFile(logFile, logLine); } /** * Get process info */ getInfo() { return { isRunning: this.isRunning, pid: this.process?.pid, uptime: this.process ? Date.now() - this.process.startTime : 0, bufferSize: this.outputBuffer.length }; } }; // src/core/claude-activity-handler.ts import { EventEmitter as EventEmitter4 } from "events"; import express2 from "express"; import { watch as watch2 } from "chokidar"; import * as fs4 from "fs-extra"; import * as path4 from "path"; var ClaudeActivityHandler = class extends EventEmitter4 { projectRoot; claudeDir; watcher; activities = []; maxActivities = 1e3; constructor(projectRoot) { super(); this.projectRoot = projectRoot; this.claudeDir = path4.join(projectRoot, ".claude"); } async start() { await fs4.ensureDir(path4.join(this.claudeDir, "logs")); this.startWatcher(); await this.parseExistingLogs(); logger.info("Claude activity handler started"); } async stop() { if (this.watcher) { await this.watcher.close(); } logger.info("Claude activity handler stopped"); } /** * Add SSE endpoints to express app */ setupSSEEndpoints(app) { app.get("/mcp/claude/stream", (req, res) => { res.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", "Connection": "keep-alive", "Access-Control-Allow-Origin": "*" }); res.write(`event: connected `); res.write(`data: ${JSON.stringify({ type: "connected", activities: this.activities.slice(-10) })} `); const activityHandler = (activity) => { res.write(`event: activity `); res.write(`data: ${JSON.stringify(activity)} `); }; this.on("activity", activityHandler); const keepAlive = setInterval(() => { res.write(":keepalive\n\n"); }, 3e4); req.on("close", () => { this.off("activity", activityHandler); clearInterval(keepAlive); }); }); app.get("/mcp/claude/summary", (req, res) => { const summary = this.getActivitySummary(); res.json(summary); }); app.get("/mcp/claude/current-task", async (req, res) => { const task = await this.getCurrentTask(); res.json(task); }); app.post("/mcp/claude/log", express2.json(), (req, res) => { const { type, data } = req.body; this.logActivity(type, data); res.json({ success: true }); }); } /** * Start file watcher */ startWatcher() { this.watcher = watch2(this.claudeDir, { persistent: true, ignoreInitial: true, depth: 3 }); this.watcher.on("change", async (filePath) => { if (filePath.includes("logs/")) { await this.parseLogFile(filePath, true); } else if (filePath.includes("current-task.md")) { const content = await fs4.readFile(filePath, "utf-8"); this.addActivity({ type: "task-update", data: { file: filePath, content }, source: "file" }); } }); this.watcher.on("add", async (filePath) => { if (filePath.includes("logs/") && filePath.endsWith(".log")) { await this.parseLogFile(filePath); } }); } /** * Parse existing log files */ async parseExistingLogs() { const logsDir = path4.join(this.claudeDir, "logs"); try { const files = await fs4.readdir(logsDir); for (const file of files) { if (file.endsWith(".log")) { await this.parseLogFile(path4.join(logsDir, file)); } } } catch (error) { logger.debug("No existing logs to parse"); } } /** * Parse a log file */ async parseLogFile(filePath, tailOnly = false) { try { const content = await fs4.readFile(filePath, "utf-8"); const lines = content.split("\n").filter((line) => line.trim()); const linesToProcess = tailOnly ? lines.slice(-20) : lines; for (const line of linesToProcess) { const activity = this.parseLogLine(line); if (activity) { this.addActivity(activity); } } } catch (error) { logger.error(`Failed to parse log file ${filePath}:`, error); } } /** * Parse a log line */ parseLogLine(line) { const toolMatch = line.match(/^\[(.+?)\] (PRE_TOOL|POST_TOOL|TOOL_START|TOOL_END): (\w+) \| (.+)$/); if (toolMatch) { return { type: "tool-use", data: { phase: toolMatch[2], tool: toolMatch[3], details: this.parseDetails(toolMatch[4]) }, source: "hook" }; } const fileMatch = line.match(/^\[(.+?)\] FILE_MODIFIED: (.+)$/); if (fileMatch) { return { type: "file-change", data: { file: fileMatch[2] }, source: "hook" }; } const cmdMatch = line.match(/^\[(.+?)\] COMMAND_RUN: (.+)$/); if (cmdMatch) { return { type: "command", data: { command: cmdMatch[2] }, source: "hook" }; } const sessionMatch = line.match(/^\[(.+?)\] (SESSION_START|SESSION_END)$/); if (sessionMatch) { return { type: "session", data: { event: sessionMatch[2] }, source: "hook" }; } return null; } /** * Parse details from log line */ parseDetails(details) { try { return JSON.parse(details); } catch { const pairs = details.split(" | "); const result = {}; for (const pair of pairs) { const [key, value] = pair.split(": "); if (key && value) { result[key.toLowerCase().replace(/\s+/g, "_")] = value; } } return Object.keys(result).length > 0 ? result : details; } } /** * Add activity to the list */ addActivity(activity) { const fullActivity = { id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, timestamp: (/* @__PURE__ */ new Date()).toISOString(), ...activity }; this.activities.push(fullActivity); if (this.activities.length > this.maxActivities) { this.activities = this.activities.slice(-this.maxActivities); } this.emit("activity", fullActivity); } /** * Log an activity manually */ logActivity(type, data) { this.addActivity({ type, data, source: "monitor" }); } /** * Get activity summary */ getActivitySummary() { const summary = { totalActivities: this.activities.length, recentActivities: this.activities.slice(-20), byType: {}, bySource: {}, toolUsage: {}, fileChanges: [] }; for (const activity of this.activities) { summary.byType[activity.type] = (summary.byType[activity.type] || 0) + 1; summary.bySource[activity.source] = (summary.bySource[activity.source] || 0) + 1; if (activity.type === "tool-use" && activity.data.tool) { summary.toolUsage[activity.data.tool] = (summary.toolUsage[activity.data.tool] || 0) + 1; } if (activity.type === "file-change" && activity.data.file) { if (!summary.fileChanges.includes(activity.data.file)) { summary.fileChanges.push(activity.data.file); } } } return summary; } /** * Get current task */ async getCurrentTask() { const taskFile = path4.join(this.claudeDir, "current-task.md"); try { const content = await fs4.readFile(taskFile, "utf-8"); const idMatch = content.match(/## Task ID: (.+)/); const descMatch = content.match(/## Description\n(.+)/); const priorityMatch = content.match(/## Priority: (.+)/); const startedMatch = content.match(/- Started: (.+)/); return { exists: true, id: idMatch?.[1], description: descMatch?.[1], priority: priorityMatch?.[1], startedAt: startedMatch?.[1], content }; } catch { return { exists: false }; } } }; // src/index.ts var CLAUDE_DIR = ".claude"; var CLAUDE_LOGS_DIR = ".claude/logs"; var CLAUDE_HOOKS_FILE = ".claude/settings.local.json"; var CLAUDE_TASK_FILE = ".claude/current-task.md"; var version = "0.1.0"; export { CLAUDE_DIR, CLAUDE_HOOKS_FILE, CLAUDE_LOGS_DIR, CLAUDE_TASK_FILE, ClaudeActivityHandler, ClaudeController, ClaudeMonitor, ClaudeOutputParser, ClaudeProcessManager, createLogger, version }; //# sourceMappingURL=index.js.map