UNPKG

@posthog/agent

Version:

TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog

153 lines (139 loc) 6.11 kB
import { existsSync, promises } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { Logger } from './utils/logger.js'; const logger = new Logger({ prefix: "[TemplateManager]" }); class TemplateManager { templatesDir; constructor() { const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Exhaustive list of possible template locations const candidateDirs = [ // Standard build output (dist/src/template-manager.js -> dist/templates) join(__dirname, "..", "templates"), // If preserveModules creates nested structure (dist/src/template-manager.js -> dist/src/templates) join(__dirname, "templates"), // Development scenarios (src/template-manager.ts -> src/templates) join(__dirname, "..", "..", "src", "templates"), // Package root templates directory join(__dirname, "..", "..", "templates"), // When node_modules symlink or installed (node_modules/@posthog/agent/dist/src/... -> node_modules/@posthog/agent/dist/templates) join(__dirname, "..", "..", "dist", "templates"), // When consumed from node_modules deep in tree join(__dirname, "..", "..", "..", "templates"), join(__dirname, "..", "..", "..", "dist", "templates"), join(__dirname, "..", "..", "..", "src", "templates"), // When bundled by Vite/Webpack (e.g., .vite/build/index.js -> node_modules/@posthog/agent/dist/templates) // Try to find node_modules from current location join(__dirname, "..", "node_modules", "@posthog", "agent", "dist", "templates"), join(__dirname, "..", "..", "node_modules", "@posthog", "agent", "dist", "templates"), join(__dirname, "..", "..", "..", "node_modules", "@posthog", "agent", "dist", "templates"), ]; const resolvedDir = candidateDirs.find((dir) => existsSync(dir)); if (!resolvedDir) { logger.error("Could not find templates directory."); logger.error(`Current file: ${__filename}`); logger.error(`Current dir: ${__dirname}`); logger.error(`Tried: ${candidateDirs.map((d) => `\n - ${d} (exists: ${existsSync(d)})`).join("")}`); } this.templatesDir = resolvedDir ?? candidateDirs[0]; } async loadTemplate(templateName) { try { const templatePath = join(this.templatesDir, templateName); return await promises.readFile(templatePath, "utf8"); } catch (error) { throw new Error(`Failed to load template ${templateName} from ${this.templatesDir}: ${error}`); } } substituteVariables(template, variables) { let result = template; for (const [key, value] of Object.entries(variables)) { if (value !== undefined) { const placeholder = new RegExp(`{{${key}}}`, "g"); result = result.replace(placeholder, value); } } result = result.replace(/{{[^}]+}}/g, "[PLACEHOLDER]"); return result; } async generatePlan(variables) { const template = await this.loadTemplate("plan-template.md"); return this.substituteVariables(template, { ...variables, date: variables.date || new Date().toISOString().split("T")[0], }); } async generateCustomFile(templateName, variables) { const template = await this.loadTemplate(templateName); return this.substituteVariables(template, { ...variables, date: variables.date || new Date().toISOString().split("T")[0], }); } async createTaskStructure(taskId, taskTitle, options) { const files = []; const variables = { task_id: taskId, task_title: taskTitle, date: new Date().toISOString().split("T")[0], }; // Generate plan file if requested if (options?.includePlan !== false) { const planContent = await this.generatePlan(variables); files.push({ name: "plan.md", content: planContent, type: "plan", }); } if (options?.additionalFiles) { for (const file of options.additionalFiles) { let content; if (file.template) { content = await this.generateCustomFile(file.template, variables); } else if (file.content) { content = this.substituteVariables(file.content, variables); } else { content = `# ${file.name}\n\nPlaceholder content for ${file.name}`; } files.push({ name: file.name, content, type: file.name.includes("context") ? "context" : "reference", }); } } return files; } generatePostHogReadme() { return `# PostHog Task Files This directory contains task-related files generated by the PostHog Agent. ## Structure Each task has its own subdirectory: \`.posthog/{task-id}/\` ### Common Files - **plan.md** - Implementation plan generated during planning phase - **Supporting files** - Any additional files added for task context - **artifacts/** - Generated files, outputs, and temporary artifacts ### Usage These files are: - Version controlled alongside your code - Used by the PostHog Agent for context - Available for review in pull requests - Organized by task ID for easy reference ### Gitignore Customize \`.posthog/.gitignore\` to control which files are committed: - Include plans and documentation by default - Exclude temporary files and sensitive data - Customize based on your team's needs --- *Generated by PostHog Agent* `; } } export { TemplateManager }; //# sourceMappingURL=template-manager.js.map