termcode
Version:
Superior terminal AI coding agent with enterprise-grade security, intelligent error recovery, performance monitoring, and plugin system - Advanced Claude Code alternative
212 lines (211 loc) • 9.05 kB
JavaScript
import { incrementMacroUsage } from "./storage.js";
import { runShell } from "../tools/shell.js";
import { runTask } from "../agent/planner.js";
import { log } from "../util/logging.js";
// Active macro executions
const activeExecutions = new Map();
// Execute a macro step
async function executeStep(step, projectPath, execution) {
const stepIndex = execution.currentStep;
const stepResult = execution.results[stepIndex];
stepResult.status = "running";
const startTime = Date.now();
try {
let output = "";
switch (step.type) {
case "command":
// Execute termcode command
log.step("Macro", `Executing command: ${step.action}`);
switch (step.action) {
case "rollback":
output = "Rollback executed";
break;
case "test":
const { runTests } = await import("../tools/test.js");
const testResult = await runTests(projectPath);
output = `Tests ${testResult.success ? "passed" : "failed"}: ${testResult.output}`;
if (!testResult.success)
throw new Error("Tests failed");
break;
case "lint":
const { runLinter } = await import("../tools/test.js");
const lintResult = await runLinter(projectPath);
output = `Lint ${lintResult.success ? "passed" : "failed"}: ${lintResult.output}`;
if (!lintResult.success)
throw new Error("Lint failed");
break;
case "build":
const { runBuild } = await import("../tools/test.js");
const buildResult = await runBuild(projectPath);
output = `Build ${buildResult.success ? "passed" : "failed"}: ${buildResult.output}`;
if (!buildResult.success)
throw new Error("Build failed");
break;
default:
throw new Error(`Unknown command: ${step.action}`);
}
break;
case "task":
// Execute AI task
log.step("Macro", `Executing task: ${step.action}`);
const taskResult = await runTask(projectPath, step.action);
output = `Applied ${taskResult?.applied.length || 0} changes, rejected ${taskResult?.rejected.length || 0}`;
break;
case "shell":
// Execute shell command
log.step("Macro", `Executing shell: ${step.action}`);
const shellArgs = step.args || step.action.split(" ");
const shellResult = await runShell(shellArgs, projectPath);
if (!shellResult.ok) {
throw new Error('error' in shellResult ? shellResult.error : "Shell command failed");
}
output = shellResult.data.stdout + shellResult.data.stderr;
break;
case "git":
// Execute git command
log.step("Macro", `Executing git: ${step.action}`);
const gitArgs = ["git", ...step.action.split(" ")];
const gitResult = await runShell(gitArgs, projectPath);
if (!gitResult.ok) {
throw new Error('error' in gitResult ? gitResult.error : "Git command failed");
}
output = gitResult.data.stdout + gitResult.data.stderr;
break;
case "wait":
// Wait for specified time
const waitTime = parseInt(step.action) || 1000;
log.step("Macro", `Waiting ${waitTime}ms`);
await new Promise(resolve => setTimeout(resolve, waitTime));
output = `Waited ${waitTime}ms`;
break;
default:
throw new Error(`Unknown step type: ${step.type}`);
}
stepResult.status = "completed";
stepResult.output = output;
stepResult.duration = Date.now() - startTime;
return { success: true, output };
}
catch (error) {
stepResult.status = "failed";
stepResult.error = error instanceof Error ? error.message : "Unknown error";
stepResult.duration = Date.now() - startTime;
return {
success: false,
error: error instanceof Error ? error.message : "Unknown error"
};
}
}
// Execute a complete macro
export async function executeMacro(macro, projectPath) {
const executionId = `${macro.name}-${Date.now()}`;
const execution = {
macroName: macro.name,
startTime: new Date().toISOString(),
status: "running",
currentStep: 0,
totalSteps: macro.steps.length,
results: macro.steps.map((_, index) => ({
step: index,
status: "pending"
}))
};
activeExecutions.set(executionId, execution);
try {
log.info(`Starting macro: ${macro.name} (${macro.steps.length} steps)`);
// Execute each step
for (let i = 0; i < macro.steps.length; i++) {
execution.currentStep = i;
const step = macro.steps[i];
log.step(`Step ${i + 1}/${macro.steps.length}`, step.description || step.action);
const result = await executeStep(step, projectPath, execution);
if (!result.success) {
execution.status = "failed";
execution.error = result.error;
execution.endTime = new Date().toISOString();
log.error(`Macro failed at step ${i + 1}: ${result.error}`);
return execution;
}
}
execution.status = "completed";
execution.endTime = new Date().toISOString();
// Increment usage count
await incrementMacroUsage(macro.name, projectPath);
log.success(`Macro completed: ${macro.name}`);
return execution;
}
catch (error) {
execution.status = "failed";
execution.error = error instanceof Error ? error.message : "Unknown error";
execution.endTime = new Date().toISOString();
log.error(`Macro execution failed: ${execution.error}`);
return execution;
}
finally {
activeExecutions.delete(executionId);
}
}
// Cancel a running macro
export async function cancelMacro(executionId) {
const execution = activeExecutions.get(executionId);
if (!execution)
return false;
execution.status = "cancelled";
execution.endTime = new Date().toISOString();
activeExecutions.delete(executionId);
return true;
}
// Get active executions
export function getActiveExecutions() {
return Array.from(activeExecutions.values());
}
// Built-in macro templates
export const BUILTIN_MACROS = [
{
name: "hotfix",
description: "Quick hotfix workflow: lint, test, commit",
steps: [
{ type: "command", action: "lint", description: "Run linter" },
{ type: "command", action: "test", description: "Run tests" },
{ type: "git", action: "add .", description: "Stage changes" },
{ type: "git", action: "commit -m 'hotfix: automated fix'", description: "Commit changes" }
],
tags: ["workflow", "fix", "ci"],
scope: "global",
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
usageCount: 0,
author: "termcode"
},
{
name: "deploy-prep",
description: "Prepare for deployment: build, test, lint",
steps: [
{ type: "command", action: "lint", description: "Run linter" },
{ type: "command", action: "test", description: "Run tests" },
{ type: "command", action: "build", description: "Build project" },
{ type: "shell", action: "echo 'Ready for deployment!'", description: "Deployment ready" }
],
tags: ["deployment", "ci", "build"],
scope: "global",
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
usageCount: 0,
author: "termcode"
},
{
name: "clean-start",
description: "Clean restart: rollback, clean, fresh start",
steps: [
{ type: "command", action: "rollback", description: "Rollback changes" },
{ type: "git", action: "clean -fd", description: "Clean working directory" },
{ type: "shell", action: "echo 'Clean state ready'", description: "Ready for new work" }
],
tags: ["cleanup", "reset"],
scope: "global",
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
usageCount: 0,
author: "termcode"
}
];