import type { ExtensionAPI } from "@jaeswift/jae-coding-agent"; import { existsSync, readFileSync, writeFileSync } from "node:fs"; import { join, basename } from "node:path"; import { Type } from "@sinclair/typebox"; export default function (pi: ExtensionAPI) { async function analyzeAndGenerate(cwd: string, ctx: any): Promise { const sections: string[] = []; // Project name let projectName = basename(cwd); let description = ""; const pkgPath = join(cwd, "package.json"); if (existsSync(pkgPath)) { try { const pkg = JSON.parse(readFileSync(pkgPath, "utf-8")); projectName = pkg.name || projectName; description = pkg.description || ""; } catch { /* ignore */ } } sections.push(`# ${projectName}\n`); if (description) sections.push(`${description}\n`); // Tech stack const stackChecks = [ { file: "package.json", label: "Node.js/JavaScript" }, { file: "tsconfig.json", label: "TypeScript" }, { file: "Cargo.toml", label: "Rust" }, { file: "go.mod", label: "Go" }, { file: "pyproject.toml", label: "Python" }, { file: "requirements.txt", label: "Python" }, { file: "Dockerfile", label: "Docker" }, { file: "docker-compose.yml", label: "Docker Compose" }, { file: "next.config.js", label: "Next.js" }, { file: "next.config.mjs", label: "Next.js" }, { file: "vite.config.ts", label: "Vite" }, { file: "tailwind.config.js", label: "Tailwind CSS" }, { file: "tailwind.config.ts", label: "Tailwind CSS" }, ]; const stack = [...new Set(stackChecks.filter((c) => existsSync(join(cwd, c.file))).map((c) => c.label))]; if (stack.length > 0) { sections.push(`## Tech Stack\n${stack.map((s) => `- ${s}`).join("\n")}\n`); } // Prerequisites sections.push(`## Prerequisites\n`); if (existsSync(join(cwd, "package.json"))) sections.push(`- Node.js (v18+ recommended)\n- npm or yarn\n`); if (existsSync(join(cwd, "requirements.txt"))) sections.push(`- Python 3.8+\n- pip\n`); if (existsSync(join(cwd, "Cargo.toml"))) sections.push(`- Rust / cargo\n`); if (existsSync(join(cwd, "Dockerfile"))) sections.push(`- Docker\n`); // Installation sections.push(`## Installation\n\n\`\`\`bash\ngit clone \ncd ${basename(cwd)}\n`); if (existsSync(join(cwd, "package.json"))) sections.push(`npm install\n`); if (existsSync(join(cwd, "requirements.txt"))) sections.push(`pip install -r requirements.txt\n`); sections.push(`\`\`\`\n`); // Scripts if (existsSync(pkgPath)) { try { const pkg = JSON.parse(readFileSync(pkgPath, "utf-8")); if (pkg.scripts && Object.keys(pkg.scripts).length > 0) { sections.push(`## Available Scripts\n`); for (const [name, cmd] of Object.entries(pkg.scripts).slice(0, 20) as [string, string][]) { sections.push(`- \`npm run ${name}\` - ${cmd}`); } sections.push(""); } } catch { /* ignore */ } } // Project structure try { const treeResult = await pi.exec( "find", [".", "-maxdepth", "2", "-type", "f", "-not", "-path", "*/node_modules/*", "-not", "-path", "*/.git/*", "-not", "-path", "*/dist/*"], { cwd }, ); if (treeResult.code === 0 && treeResult.stdout.trim()) { const files = treeResult.stdout.trim().split("\n").sort().slice(0, 40); sections.push(`## Project Structure\n\n\`\`\`\n${files.join("\n")}\n\`\`\`\n`); } } catch { /* ignore */ } // Env vars const envExample = join(cwd, ".env.example"); if (existsSync(envExample)) { const envContent = readFileSync(envExample, "utf-8").trim(); sections.push(`## Environment Variables\n\nCopy \`.env.example\` to \`.env\` and configure:\n\n\`\`\`\n${envContent}\n\`\`\`\n`); } // License const licensePath = join(cwd, "LICENSE"); if (existsSync(licensePath)) { const license = readFileSync(licensePath, "utf-8"); const firstLine = license.split("\n")[0].trim(); sections.push(`## License\n\n${firstLine || "See LICENSE file."}\n`); } sections.push(`\n---\n*Auto-generated by Agent JAE on ${new Date().toISOString().split("T")[0]}*\n`); return sections.join("\n"); } pi.registerTool({ name: "generate_docs", label: "Generate Documentation", description: "Auto-generate README and documentation for the current project", parameters: Type.Object({ output: Type.Optional(Type.String({ description: "Output filename (default: README.md)" })), }), execute: async (args, ctx) => { const filename = args.output || "README.md"; const content = await analyzeAndGenerate(ctx.cwd, ctx); const outPath = join(ctx.cwd, filename); writeFileSync(outPath, content, "utf-8"); return { output: `Documentation generated: ${outPath} (${content.length} chars)` }; }, }); pi.registerCommand("docs", { description: "Auto-generate README & docs: /docs [filename]", handler: async (args, ctx) => { const filename = args.trim() || "README.md"; ctx.ui.notify("\u{1F4DD} Analyzing project and generating documentation...", "info"); const content = await analyzeAndGenerate(ctx.cwd, ctx); const outPath = join(ctx.cwd, filename); if (existsSync(outPath)) { const confirmed = await ctx.ui.confirm( "Overwrite?", `${filename} already exists. Overwrite?`, ); if (!confirmed) { ctx.ui.notify("Cancelled.", "info"); return; } } writeFileSync(outPath, content, "utf-8"); ctx.ui.notify(`\u{1F4DD} Documentation generated: ${outPath} (${content.length} chars)`, "info"); }, }); }