Agent-JAE/default-extensions/deploy.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

134 lines
5.9 KiB
TypeScript

import type { ExtensionAPI } from "@jaeswift/jae-coding-agent";
import { existsSync, readFileSync } from "node:fs";
import { join, basename } from "node:path";
import { Type } from "@sinclair/typebox";
type DeployTarget = "docker" | "vercel" | "rsync" | "static" | "unknown";
interface StackInfo {
type: DeployTarget;
runtime: string;
entrypoint?: string;
}
export default function (pi: ExtensionAPI) {
function detectStack(cwd: string): StackInfo {
if (existsSync(join(cwd, "Dockerfile"))) {
return { type: "docker", runtime: "Docker", entrypoint: "Dockerfile" };
}
if (existsSync(join(cwd, "docker-compose.yml"))) {
return { type: "docker", runtime: "Docker Compose", entrypoint: "docker-compose.yml" };
}
if (existsSync(join(cwd, "vercel.json"))) {
return { type: "vercel", runtime: "Vercel" };
}
if (existsSync(join(cwd, "package.json"))) {
try {
const pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf-8"));
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
if (deps["next"]) return { type: "vercel", runtime: "Next.js" };
if (deps["vite"] || deps["react-scripts"]) return { type: "static", runtime: "SPA (Vite/CRA)" };
if (pkg.scripts?.start) return { type: "docker", runtime: "Node.js" };
} catch { /* ignore */ }
return { type: "docker", runtime: "Node.js" };
}
if (existsSync(join(cwd, "requirements.txt")) || existsSync(join(cwd, "pyproject.toml"))) {
return { type: "docker", runtime: "Python" };
}
if (existsSync(join(cwd, "index.html"))) {
return { type: "static", runtime: "Static HTML" };
}
return { type: "unknown", runtime: "Unknown" };
}
pi.registerTool({
name: "deploy",
label: "Deploy Project",
description: "Detect project stack and deploy. Supports Docker, Vercel, rsync, and static sites.",
parameters: Type.Object({
target: Type.Optional(Type.String({ description: "Deploy target: docker, vercel, rsync, static (auto-detected if omitted)" })),
host: Type.Optional(Type.String({ description: "Remote host for rsync deploy (user@host:/path)" })),
tag: Type.Optional(Type.String({ description: "Docker image tag" })),
}),
execute: async (args, ctx) => {
const stack = detectStack(ctx.cwd);
const deployType = (args.target as DeployTarget) || stack.type;
if (deployType === "unknown") {
return { error: "Could not detect project stack. Specify a target: docker, vercel, rsync, static" };
}
const results: string[] = [`Detected: ${stack.runtime}`, `Deploy target: ${deployType}`];
if (deployType === "docker") {
const tag = args.tag || basename(ctx.cwd).toLowerCase();
const buildResult = await pi.exec("docker", ["build", "-t", tag, "."], { cwd: ctx.cwd });
if (buildResult.code !== 0) {
return { error: `Docker build failed: ${buildResult.stderr}` };
}
results.push(`Docker image built: ${tag}`);
results.push(`Run with: docker run -d -p 3000:3000 ${tag}`);
} else if (deployType === "vercel") {
const vercelResult = await pi.exec("npx", ["vercel", "--yes"], { cwd: ctx.cwd });
results.push(vercelResult.code === 0 ? `Vercel deployed: ${vercelResult.stdout.trim()}` : `Vercel error: ${vercelResult.stderr}`);
} else if (deployType === "rsync") {
if (!args.host) {
return { error: "rsync deploy requires --host argument (user@host:/path)" };
}
const rsyncResult = await pi.exec(
"rsync",
["-avz", "--exclude", "node_modules", "--exclude", ".git", "./", args.host],
{ cwd: ctx.cwd },
);
results.push(rsyncResult.code === 0 ? `Synced to ${args.host}` : `rsync error: ${rsyncResult.stderr}`);
} else if (deployType === "static") {
// Build if needed
const pkgPath = join(ctx.cwd, "package.json");
if (existsSync(pkgPath)) {
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
if (pkg.scripts?.build) {
const buildResult = await pi.exec("npm", ["run", "build"], { cwd: ctx.cwd });
results.push(buildResult.code === 0 ? "Build succeeded" : `Build error: ${buildResult.stderr}`);
}
}
const distDir = existsSync(join(ctx.cwd, "dist")) ? "dist" : existsSync(join(ctx.cwd, "build")) ? "build" : ".";
results.push(`Static files in: ${distDir}/`);
results.push(`Serve locally: npx serve ${distDir}`);
if (args.host) {
const rsyncResult = await pi.exec("rsync", ["-avz", `${distDir}/`, args.host], { cwd: ctx.cwd });
results.push(rsyncResult.code === 0 ? `Deployed to ${args.host}` : `rsync error: ${rsyncResult.stderr}`);
}
}
return { output: results.join("\n") };
},
});
pi.registerCommand("deploy", {
description: "Deploy project: /deploy [docker|vercel|rsync|static] [--host user@host:/path] [--tag image-tag]",
handler: async (args, ctx) => {
const stack = detectStack(ctx.cwd);
const parts = args.trim().split(/\s+/);
const target = parts[0] || undefined;
let host: string | undefined;
let tag: string | undefined;
for (let i = 0; i < parts.length; i++) {
if (parts[i] === "--host" && parts[i + 1]) host = parts[i + 1];
if (parts[i] === "--tag" && parts[i + 1]) tag = parts[i + 1];
}
if (!target) {
ctx.ui.notify(
`\u{1F680} Stack detected: ${stack.runtime} (${stack.type})\n\nUsage: /deploy [target] [options]\n Targets: docker, vercel, rsync, static\n Options: --host user@host:/path --tag image-name`,
"info",
);
return;
}
ctx.ui.notify(`\u{1F680} Deploying as ${target}...`, "info");
// Delegate to the tool
ctx.ui.notify(`Use the deploy tool for full deployment. Target: ${target}, Host: ${host || "none"}, Tag: ${tag || "auto"}`, "info");
},
});
}