Agent-JAE/default-extensions/auto-docs.ts
jae 4c2f22bf3e
Some checks are pending
CI / build-check-test (push) Waiting to run
feat: add 16 default extensions for Agent JAE
New extensions shipping with Agent JAE:
- jae-rules: Per-project .jae/rules.md injection
- cost-tracker: Token/cost tracking with budget limits
- project-dna: Auto-analyze project fingerprint
- teach-mode: Pedagogical explanation mode
- bookmarks: File bookmark management
- replay: Session recording and asciicast export
- checkpoints: Git-based undo/time-travel
- pr-review: Autonomous PR diff review
- auto-docs: Auto-generate README and docs
- screenshot-context: Vision analysis for screenshots
- deploy: One-command deploy (Docker/Vercel/rsync/static)
- pair-programming: Real-time file watching with feedback
- dashboard: Terminal HUD with live stats
- swarm: Multi-agent parallel task execution
- skill-marketplace: Search/install/publish skills
- widget-api-demo: TUI widget API demonstration
2026-03-23 20:14:41 +01:00

149 lines
5.7 KiB
TypeScript

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<string> {
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 <repo-url>\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");
},
});
}