@sirmrmarty/n8n-nodes-tmux-orchestrator
Version:
n8n nodes for orchestrating Claude AI agents through tmux sessions
880 lines (878 loc) • 38.1 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.TmuxProjectOrchestrator = void 0;
const n8n_workflow_1 = require("n8n-workflow");
const tmuxBridge_1 = require("../../utils/tmuxBridge");
const modelManager_1 = require("../../utils/modelManager");
const fs_1 = require("fs");
const path = __importStar(require("path"));
const child_process_1 = require("child_process");
class TmuxProjectOrchestrator {
constructor() {
this.scheduleFile = path.join(process.cwd(), '.n8n-project-schedule.json');
this.methods = {
loadOptions: {
async getAvailableModels() {
try {
return await modelManager_1.modelManager.getModelOptions();
}
catch (error) {
console.error('Failed to load models:', error);
return [
{ name: 'Claude Sonnet 4', value: 'claude-sonnet-4' },
{ name: 'Claude Opus 4.1', value: 'claude-opus-4.1' },
{ name: 'Claude Sonnet 3.5', value: 'claude-sonnet-3.5' },
{ name: 'Claude Haiku 3.5', value: 'claude-haiku-3.5' },
];
}
},
},
};
this.description = {
displayName: 'Tmux Project Orchestrator (Simplified)',
name: 'tmuxProjectOrchestrator',
group: ['transform'],
version: 1,
subtitle: '={{$parameter["operation"]}}',
description: 'Simplified project management with Claude Code integration',
defaults: {
name: 'Tmux Project Orchestrator',
},
inputs: ["main"],
outputs: ["main"],
credentials: [
{
name: 'tmuxOrchestratorApi',
required: false,
},
],
properties: [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Create Project',
value: 'createProject',
description: 'Initialize project and generate plan with Claude Code',
},
{
name: 'Approve & Execute / Schedule',
value: 'approveExecute',
description: 'Approve plan and execute immediately or schedule for later',
},
{
name: 'Get Status',
value: 'getStatus',
description: 'Get project status and scheduling information',
},
],
default: 'createProject',
},
{
displayName: 'Project Prompt',
name: 'prompt',
type: 'string',
typeOptions: {
rows: 4,
},
default: '',
placeholder: 'Describe your project idea in detail...',
description: 'The project description that will be sent to Claude Code for planning',
displayOptions: {
show: {
operation: ['createProject'],
},
},
},
{
displayName: 'Session Name',
name: 'sessionName',
type: 'string',
default: '',
placeholder: 'my-project-session',
description: 'Unique name for the tmux session',
displayOptions: {
show: {
operation: ['createProject'],
},
},
},
{
displayName: 'Reference',
name: 'reference',
type: 'string',
default: '',
placeholder: 'Optional project reference or ticket number',
description: 'Optional reference information for the project',
displayOptions: {
show: {
operation: ['createProject'],
},
},
},
{
displayName: 'Project Path',
name: 'projectPath',
type: 'string',
default: '',
placeholder: '/path/to/project',
description: 'Path where the project will be created or executed',
displayOptions: {
show: {
operation: ['createProject'],
},
},
},
{
displayName: 'Use GitHub Worktree',
name: 'useGitWorktree',
type: 'boolean',
default: false,
description: 'Whether to use Git worktree for the project',
displayOptions: {
show: {
operation: ['createProject'],
},
},
},
{
displayName: 'Auto Commit & PR',
name: 'autoCommit',
type: 'boolean',
default: true,
description: 'Automatically commit changes and create PR after project execution',
displayOptions: {
show: {
operation: ['createProject'],
},
},
},
{
displayName: 'Model Choice',
name: 'model',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getAvailableModels',
},
default: 'claude-sonnet-4',
description: 'AI model to use for project planning. Latest Claude models are recommended.',
displayOptions: {
show: {
operation: ['createProject'],
},
},
},
{
displayName: 'Project ID',
name: 'projectId',
type: 'string',
default: '',
placeholder: 'project-id-from-create-operation',
description: 'Project ID from the Create Project operation (leave empty to use session name or reference)',
displayOptions: {
show: {
operation: ['approveExecute', 'getStatus'],
},
},
},
{
displayName: 'Session Name',
name: 'sessionName',
type: 'string',
default: '',
placeholder: 'my-project-session',
description: 'Session name for project lookup (alternative to Project ID)',
displayOptions: {
show: {
operation: ['approveExecute', 'getStatus'],
},
},
},
{
displayName: 'Reference',
name: 'reference',
type: 'string',
default: '',
placeholder: 'ticket-123 or project-ref',
description: 'Reference identifier for project lookup (alternative to Project ID)',
displayOptions: {
show: {
operation: ['approveExecute', 'getStatus'],
},
},
},
{
displayName: 'Execution Mode',
name: 'executionMode',
type: 'options',
options: [
{
name: 'Execute Now',
value: 'now',
description: 'Execute the project immediately',
},
{
name: 'Schedule for Later',
value: 'schedule',
description: 'Schedule the project for later execution',
},
],
default: 'now',
description: 'Whether to execute immediately or schedule',
displayOptions: {
show: {
operation: ['approveExecute'],
},
},
},
{
displayName: 'Schedule Type',
name: 'scheduleType',
type: 'options',
options: [
{
name: 'Custom Time',
value: 'custom',
description: 'Choose a specific date and time',
},
{
name: 'Day Hours (9:00 AM)',
value: 'day',
description: 'Schedule for 9:00 AM (any day including weekends)',
},
{
name: 'Tonight (2:00 AM off-hours)',
value: 'night',
description: 'Schedule for 2:00 AM tonight for off-hours processing',
},
],
default: 'custom',
description: 'Choose how to schedule the project execution',
displayOptions: {
show: {
operation: ['approveExecute'],
executionMode: ['schedule'],
},
},
},
{
displayName: 'Schedule Time',
name: 'scheduleTime',
type: 'dateTime',
default: '',
description: 'When to execute the scheduled project',
displayOptions: {
show: {
operation: ['approveExecute'],
executionMode: ['schedule'],
scheduleType: ['custom'],
},
},
},
{
displayName: 'Priority',
name: 'priority',
type: 'options',
options: [
{
name: 'Low',
value: 'low',
},
{
name: 'Normal',
value: 'normal',
},
{
name: 'High',
value: 'high',
},
],
default: 'normal',
description: 'Project execution priority',
displayOptions: {
show: {
operation: ['approveExecute'],
executionMode: ['schedule'],
},
},
},
],
};
}
async execute() {
const items = this.getInputData();
const returnData = [];
for (let i = 0; i < items.length; i++) {
try {
const operation = this.getNodeParameter('operation', i);
let result;
const nodeInstance = new TmuxProjectOrchestrator();
switch (operation) {
case 'createProject':
result = await nodeInstance.createProject(this, i);
break;
case 'approveExecute':
result = await nodeInstance.approveExecute(this, i);
break;
case 'getStatus':
result = await nodeInstance.getStatus(this, i);
break;
default:
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown operation: ${operation}`, {
itemIndex: i,
});
}
returnData.push({
json: result,
pairedItem: {
item: i,
},
});
}
catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: { error: error.message },
pairedItem: { item: i },
});
continue;
}
throw error;
}
}
return [returnData];
}
async createProject(context, itemIndex) {
const prompt = context.getNodeParameter('prompt', itemIndex);
const sessionName = context.getNodeParameter('sessionName', itemIndex);
const reference = context.getNodeParameter('reference', itemIndex, '');
const projectPath = context.getNodeParameter('projectPath', itemIndex);
const useGitWorktree = context.getNodeParameter('useGitWorktree', itemIndex);
const autoCommit = context.getNodeParameter('autoCommit', itemIndex);
const model = context.getNodeParameter('model', itemIndex);
if (!prompt || !sessionName || !projectPath) {
throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'Prompt, session name, and project path are required');
}
const isValidModel = await modelManager_1.modelManager.isValidModel(model);
if (!isValidModel) {
throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Invalid model selection: ${model}. Please select a valid model.`);
}
const sanitizedPath = path.resolve(projectPath);
const sanitizedSessionName = sessionName.replace(/[^a-zA-Z0-9_-]/g, '_');
const projectId = `proj_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
const projectConfig = {
id: projectId,
prompt,
sessionName: sanitizedSessionName,
reference,
projectPath: sanitizedPath,
useGitWorktree,
autoCommit,
model,
status: 'created',
createdAt: new Date(),
};
const tmuxBridge = new tmuxBridge_1.TmuxBridge({
externalScriptsDir: sanitizedPath,
projectBasePath: sanitizedPath,
});
try {
await tmuxBridge.createSession(sanitizedSessionName, sanitizedPath);
projectConfig.tmuxSession = sanitizedSessionName;
projectConfig.status = 'planning';
const plan = await this.generatePlan(projectConfig, tmuxBridge);
projectConfig.plan = plan;
projectConfig.status = 'planned';
await this.saveProject(projectConfig);
return {
success: true,
projectId,
sessionName: sanitizedSessionName,
reference: reference || null,
status: 'planned',
plan,
message: 'Project created and plan generated successfully',
};
}
catch (error) {
projectConfig.status = 'failed';
await this.saveProject(projectConfig);
throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Failed to create project: ${error.message}`);
}
}
calculateDaySchedule() {
const now = new Date();
const nextSchedule = new Date(now);
nextSchedule.setHours(9, 0, 0, 0);
const currentHour = now.getHours();
if (currentHour >= 22 || currentHour < 6) {
if (currentHour < 6) {
return nextSchedule;
}
else {
nextSchedule.setDate(nextSchedule.getDate() + 1);
return nextSchedule;
}
}
if (currentHour >= 9) {
nextSchedule.setDate(nextSchedule.getDate() + 1);
}
return nextSchedule;
}
calculateNightSchedule() {
const now = new Date();
const tonight = new Date(now);
tonight.setHours(2, 0, 0, 0);
if (now.getHours() >= 2) {
tonight.setDate(tonight.getDate() + 1);
}
return tonight;
}
async approveExecute(context, itemIndex) {
const projectId = context.getNodeParameter('projectId', itemIndex, '');
const sessionName = context.getNodeParameter('sessionName', itemIndex, '');
const reference = context.getNodeParameter('reference', itemIndex, '');
const executionMode = context.getNodeParameter('executionMode', itemIndex);
let project = null;
let lookupKey = '';
if (projectId) {
project = await this.loadProject(projectId);
lookupKey = `project ID: ${projectId}`;
}
else if (sessionName) {
project = await this.loadProjectBySessionName(sessionName);
lookupKey = `session name: ${sessionName}`;
}
else if (reference) {
project = await this.loadProjectByReference(reference);
lookupKey = `reference: ${reference}`;
}
else {
throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'Project ID, session name, or reference is required');
}
if (!project) {
throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Project not found for ${lookupKey}`);
}
if (project.status !== 'planned') {
throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Project ${project.id} is not in planned status (current: ${project.status})`);
}
if (executionMode === 'now') {
return await this.executeProject(context, project);
}
else {
const scheduleType = context.getNodeParameter('scheduleType', itemIndex, 'custom');
const priority = context.getNodeParameter('priority', itemIndex, 'normal');
let scheduledDate;
let scheduleDescription;
if (scheduleType === 'custom') {
const scheduleTime = context.getNodeParameter('scheduleTime', itemIndex);
if (!scheduleTime) {
throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'Schedule time is required for custom scheduled execution');
}
scheduledDate = new Date(scheduleTime);
scheduleDescription = `custom time: ${scheduledDate.toLocaleString()}`;
}
else if (scheduleType === 'day') {
scheduledDate = this.calculateDaySchedule();
scheduleDescription = `day hours at 9:00 AM: ${scheduledDate.toLocaleString()}`;
}
else if (scheduleType === 'night') {
scheduledDate = this.calculateNightSchedule();
scheduleDescription = `tonight at 2:00 AM: ${scheduledDate.toLocaleString()}`;
}
else {
throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Invalid schedule type: ${scheduleType}`);
}
project.scheduledAt = scheduledDate;
project.priority = priority;
project.status = 'scheduled';
await this.saveProject(project);
return {
success: true,
projectId: project.id,
status: 'scheduled',
scheduledAt: project.scheduledAt,
scheduleType,
priority,
message: `Project scheduled successfully for ${scheduleDescription}`,
};
}
}
async getStatus(context, itemIndex) {
const projectId = context.getNodeParameter('projectId', itemIndex, '');
const sessionName = context.getNodeParameter('sessionName', itemIndex, '');
const reference = context.getNodeParameter('reference', itemIndex, '');
if (!projectId && !sessionName && !reference) {
const schedule = await this.loadSchedule();
return {
success: true,
totalProjects: schedule.projects.length,
activeProjects: schedule.activeProjects,
projects: schedule.projects.map(p => ({
id: p.id,
sessionName: p.sessionName,
reference: p.reference,
status: p.status,
createdAt: p.createdAt,
scheduledAt: p.scheduledAt,
priority: p.priority,
})),
};
}
let project = null;
let lookupKey = '';
if (projectId) {
project = await this.loadProject(projectId);
lookupKey = `project ID: ${projectId}`;
}
else if (sessionName) {
project = await this.loadProjectBySessionName(sessionName);
lookupKey = `session name: ${sessionName}`;
}
else if (reference) {
project = await this.loadProjectByReference(reference);
lookupKey = `reference: ${reference}`;
}
if (!project) {
throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Project not found for ${lookupKey}`);
}
return {
success: true,
project: {
id: project.id,
sessionName: project.sessionName,
reference: project.reference,
status: project.status,
createdAt: project.createdAt,
scheduledAt: project.scheduledAt,
priority: project.priority,
plan: project.plan,
tmuxSession: project.tmuxSession,
executionLog: project.executionLog,
},
};
}
async generatePlan(project, tmuxBridge) {
try {
console.log('Generating plan using autonomous Claude Code execution...');
await tmuxBridge.sendKeysToWindow(project.sessionName, 0, 'C-c');
await new Promise(resolve => setTimeout(resolve, 1000));
const modelOptions = modelManager_1.modelManager.getClaudeCommandOptions(project.model);
const autonomousCmd = `claude ${modelOptions} --dangerously-skip-permissions --max-turns 10 --print "Create a detailed implementation plan for: ${project.prompt}"`;
const success = await tmuxBridge.sendCommandToWindow(project.sessionName, 0, autonomousCmd);
if (!success) {
throw new Error('Failed to start autonomous Claude Code in tmux session');
}
await new Promise(resolve => setTimeout(resolve, 15000));
const content = await tmuxBridge.captureWindowContent(project.sessionName, 0, 100);
if (typeof content === 'string' && content.length > 50) {
const cleanContent = content
.split('\n')
.filter(line => !line.includes('claude ') && !line.includes('$') && line.trim().length > 0)
.join('\n')
.substring(0, 2000);
return cleanContent || 'Autonomous plan generated successfully';
}
throw new Error('Autonomous plan generation produced no output');
}
catch (error) {
console.error(`Autonomous plan generation failed: ${error.message}`);
await this.cleanupFailedPlanGeneration(tmuxBridge, project.sessionName, 0);
throw new Error(`Autonomous plan generation failed: ${error.message}`);
}
}
async cleanupFailedPlanGeneration(tmuxBridge, sessionName, windowIndex) {
try {
await tmuxBridge.sendKeysToWindow(sessionName, windowIndex, 'C-c');
await new Promise(resolve => setTimeout(resolve, 1000));
await tmuxBridge.sendCommandToWindow(sessionName, windowIndex, 'exit');
await new Promise(resolve => setTimeout(resolve, 1000));
await tmuxBridge.sendKeysToWindow(sessionName, windowIndex, 'C-l');
}
catch (error) {
console.warn(`Cleanup after failed plan generation encountered error: ${error.message}`);
}
}
async executeProject(context, project) {
project.status = 'executing';
project.executionLog = [];
await this.saveProject(project);
try {
const schedule = await this.loadSchedule();
const fourHoursAgo = new Date(Date.now() - 4 * 60 * 60 * 1000);
const recentActiveProjects = schedule.activeProjects.filter(activeId => {
const activeProject = schedule.projects.find(p => p.id === activeId);
return activeProject && new Date(activeProject.createdAt) > fourHoursAgo;
});
if (recentActiveProjects.length >= 4) {
throw new Error('Resource limit exceeded: Maximum 4 projects allowed within 4 hours');
}
schedule.activeProjects.push(project.id);
await this.saveSchedule(schedule);
const result = await this.runClaudeExecution(project);
let gitResult = 'Git operations skipped (autoCommit disabled)';
if (project.autoCommit) {
gitResult = await this.handleGitOperations(project);
project.executionLog?.push(`Git operations: ${gitResult}`);
}
project.status = 'completed';
project.executionLog?.push(`Execution completed at ${new Date().toISOString()}`);
schedule.activeProjects = schedule.activeProjects.filter(id => id !== project.id);
await this.saveSchedule(schedule);
await this.saveProject(project);
const message = project.autoCommit
? 'Project executed successfully with Git operations completed'
: 'Project executed successfully (Git operations skipped)';
return {
success: true,
projectId: project.id,
status: 'completed',
result,
gitResult,
message,
};
}
catch (error) {
project.status = 'failed';
project.executionLog?.push(`Execution failed at ${new Date().toISOString()}: ${error.message}`);
const schedule = await this.loadSchedule();
schedule.activeProjects = schedule.activeProjects.filter(id => id !== project.id);
await this.saveSchedule(schedule);
await this.saveProject(project);
throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Project execution failed: ${error.message}`);
}
}
async runClaudeExecution(project) {
return new Promise(async (resolve, reject) => {
try {
await this.ensureLatestVersion(project);
}
catch (pullError) {
project.executionLog?.push(`Git pull warning: ${pullError.message}`);
}
const modelOptions = modelManager_1.modelManager.getClaudeCommandOptions(project.model);
const autonomousExecutionCmd = `claude ${modelOptions} --dangerously-skip-permissions --max-turns 20 "${project.prompt}"`;
project.executionLog?.push(`Starting autonomous Claude execution: ${autonomousExecutionCmd}`);
const child = (0, child_process_1.spawn)('bash', ['-c', autonomousExecutionCmd], {
cwd: project.projectPath,
env: { ...process.env },
});
let stdout = '';
let stderr = '';
child.stdout.on('data', (data) => {
const output = data.toString();
stdout += output;
project.executionLog?.push(`STDOUT: ${output.trim()}`);
});
child.stderr.on('data', (data) => {
const output = data.toString();
stderr += output;
project.executionLog?.push(`STDERR: ${output.trim()}`);
});
child.on('close', (code) => {
if (code === 0) {
resolve(stdout || 'Execution completed successfully');
}
else {
reject(new Error(`Claude Code execution failed: ${stderr || 'Unknown error'}`));
}
});
child.on('error', (error) => {
reject(new Error(`Failed to execute Claude Code: ${error.message}`));
});
});
}
async ensureLatestVersion(project) {
const { execSync } = require('child_process');
const fs = require('fs');
try {
const gitDir = `${project.projectPath}/.git`;
if (!fs.existsSync(gitDir)) {
return;
}
const remotes = execSync('git remote -v', {
cwd: project.projectPath,
encoding: 'utf8',
timeout: 10000
});
if (!remotes || remotes.trim().length === 0) {
return;
}
project.executionLog?.push(`Pulling latest changes from Git remote...`);
execSync('git fetch origin', {
cwd: project.projectPath,
encoding: 'utf8',
timeout: 30000
});
const currentBranch = execSync('git branch --show-current', {
cwd: project.projectPath,
encoding: 'utf8',
timeout: 5000
}).trim();
try {
execSync(`git pull origin ${currentBranch}`, {
cwd: project.projectPath,
encoding: 'utf8',
timeout: 30000
});
project.executionLog?.push(`Successfully pulled latest changes from ${currentBranch}`);
}
catch (pullError) {
try {
execSync(`git pull --set-upstream origin ${currentBranch}`, {
cwd: project.projectPath,
encoding: 'utf8',
timeout: 30000
});
project.executionLog?.push(`Successfully pulled and set upstream for ${currentBranch}`);
}
catch (upstreamError) {
project.executionLog?.push(`Unable to pull latest changes: ${upstreamError.message}`);
}
}
}
catch (error) {
throw new Error(`Git pull operation failed: ${error.message}`);
}
}
async loadSchedule() {
try {
if (await fs_1.promises.access(this.scheduleFile).then(() => true).catch(() => false)) {
const data = await fs_1.promises.readFile(this.scheduleFile, 'utf8');
return JSON.parse(data);
}
}
catch (error) {
}
return {
projects: [],
activeProjects: [],
lastCleanup: new Date(),
};
}
async saveSchedule(schedule) {
await fs_1.promises.writeFile(this.scheduleFile, JSON.stringify(schedule, null, 2));
}
async loadProject(projectId) {
const schedule = await this.loadSchedule();
return schedule.projects.find(p => p.id === projectId) || null;
}
async loadProjectBySessionName(sessionName) {
const schedule = await this.loadSchedule();
return schedule.projects.find(p => p.sessionName === sessionName) || null;
}
async loadProjectByReference(reference) {
const schedule = await this.loadSchedule();
return schedule.projects.find(p => p.reference === reference) || null;
}
async saveProject(project) {
const schedule = await this.loadSchedule();
const existingIndex = schedule.projects.findIndex(p => p.id === project.id);
if (existingIndex >= 0) {
schedule.projects[existingIndex] = project;
}
else {
schedule.projects.push(project);
}
await this.saveSchedule(schedule);
}
async handleGitOperations(project) {
return new Promise((resolve) => {
const projectRef = project.reference ? ` - ${project.reference}` : '';
const commitMessage = `feat: ${project.prompt.slice(0, 50)}${project.prompt.length > 50 ? '...' : ''}${projectRef}
Generated by Claude Code automation
Project ID: ${project.id}
Model: ${project.model}
Co-authored-by: Claude Code <noreply@anthropic.com>`;
let gitCommands;
if (project.useGitWorktree) {
const branchName = `feature/${project.sessionName}`;
gitCommands = [
'git add .',
`git commit -m "${commitMessage.replace(/"/g, '\\"')}"`,
`git push -u origin ${branchName}`,
`gh pr create --title "feat: ${project.prompt.slice(0, 50)}${project.prompt.length > 50 ? '...' : ''}" --body "**Project:** ${project.sessionName}\\n\\n**Description:** ${project.prompt}\\n\\n**Reference:** ${project.reference || 'N/A'}\\n\\n**Model Used:** ${project.model}\\n\\n---\\n\\n🤖 This PR was generated automatically by Claude Code automation.\\n\\nProject ID: \`${project.id}\`"`
];
}
else {
gitCommands = [
'git add .',
`git commit -m "${commitMessage.replace(/"/g, '\\"')}"`
];
}
const fullCommand = gitCommands.join(' && ');
const child = (0, child_process_1.spawn)('bash', ['-c', fullCommand], {
cwd: project.projectPath,
env: { ...process.env }
});
let stdout = '';
let stderr = '';
child.stdout.on('data', (data) => {
const output = data.toString();
stdout += output;
project.executionLog?.push(`GIT STDOUT: ${output.trim()}`);
});
child.stderr.on('data', (data) => {
const output = data.toString();
stderr += output;
project.executionLog?.push(`GIT STDERR: ${output.trim()}`);
});
child.on('close', (code) => {
if (code === 0) {
const result = project.useGitWorktree
? `Committed changes and created pull request for worktree branch`
: `Committed changes to current branch`;
resolve(`${result}: ${stdout || 'Success'}`);
}
else {
const warning = `Git operations completed with warnings: ${stderr || 'Unknown issue'}`;
project.executionLog?.push(`GIT WARNING: ${warning}`);
resolve(warning);
}
});
child.on('error', (error) => {
const warning = `Git operations failed: ${error.message}`;
project.executionLog?.push(`GIT ERROR: ${warning}`);
resolve(warning);
});
});
}
}
exports.TmuxProjectOrchestrator = TmuxProjectOrchestrator;
//# sourceMappingURL=TmuxProjectOrchestrator.node.js.map