feat: add 16 default extensions for Agent JAE
Some checks are pending
CI / build-check-test (push) Waiting to run

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
This commit is contained in:
jae 2026-03-23 20:14:41 +01:00
parent 7fe886fea5
commit 4c2f22bf3e
16 changed files with 1797 additions and 0 deletions

View file

@ -0,0 +1,149 @@
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");
},
});
}

View file

@ -0,0 +1,132 @@
import type { ExtensionAPI } from "@jaeswift/jae-coding-agent";
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
import { join } from "node:path";
interface Bookmark {
name: string;
path: string;
line?: number;
note?: string;
timestamp: string;
}
export default function (pi: ExtensionAPI) {
let bookmarks: Bookmark[] = [];
function getBookmarksPath(cwd: string): string {
return join(cwd, ".jae", "bookmarks.json");
}
function loadBookmarks(cwd: string): void {
const bPath = getBookmarksPath(cwd);
if (existsSync(bPath)) {
try {
bookmarks = JSON.parse(readFileSync(bPath, "utf-8"));
} catch {
bookmarks = [];
}
}
}
function saveBookmarks(cwd: string): void {
const jaeDir = join(cwd, ".jae");
if (!existsSync(jaeDir)) mkdirSync(jaeDir, { recursive: true });
writeFileSync(getBookmarksPath(cwd), JSON.stringify(bookmarks, null, 2), "utf-8");
}
pi.on("session_start", async (_event, ctx) => {
loadBookmarks(ctx.cwd);
});
pi.registerCommand("bookmark", {
description: "Manage bookmarks: /bookmark [add <name> <path>[:line]] | /bookmark list | /bookmark remove <name>",
handler: async (args, ctx) => {
loadBookmarks(ctx.cwd);
const parts = args.trim().split(/\s+/);
const subcommand = parts[0] || "list";
if (subcommand === "list" || subcommand === "ls") {
if (bookmarks.length === 0) {
ctx.ui.notify("No bookmarks saved. Use /bookmark add <name> <path> to add one.", "info");
return;
}
const list = bookmarks
.map((b, i) => ` ${i + 1}. ${b.name} -> ${b.path}${b.line ? `:${b.line}` : ""}${b.note ? ` (${b.note})` : ""}`)
.join("\n");
ctx.ui.notify(`\u{1F516} Bookmarks:\n${list}`, "info");
return;
}
if (subcommand === "add") {
const name = parts[1];
const pathArg = parts[2];
const note = parts.slice(3).join(" ") || undefined;
if (!name || !pathArg) {
ctx.ui.notify("Usage: /bookmark add <name> <path>[:line] [note]", "warning");
return;
}
let filePath = pathArg;
let line: number | undefined;
if (pathArg.includes(":")) {
const colonIdx = pathArg.lastIndexOf(":");
const maybeNum = parseInt(pathArg.substring(colonIdx + 1), 10);
if (!isNaN(maybeNum)) {
filePath = pathArg.substring(0, colonIdx);
line = maybeNum;
}
}
// Remove existing bookmark with same name
bookmarks = bookmarks.filter((b) => b.name !== name);
bookmarks.push({ name, path: filePath, line, note, timestamp: new Date().toISOString() });
saveBookmarks(ctx.cwd);
ctx.ui.notify(`\u{1F516} Bookmark '${name}' saved: ${filePath}${line ? `:${line}` : ""}`, "info");
return;
}
if (subcommand === "remove" || subcommand === "rm" || subcommand === "delete") {
const name = parts[1];
if (!name) {
ctx.ui.notify("Usage: /bookmark remove <name>", "warning");
return;
}
const before = bookmarks.length;
bookmarks = bookmarks.filter((b) => b.name !== name);
if (bookmarks.length < before) {
saveBookmarks(ctx.cwd);
ctx.ui.notify(`\u{1F516} Bookmark '${name}' removed.`, "info");
} else {
ctx.ui.notify(`Bookmark '${name}' not found.`, "warning");
}
return;
}
ctx.ui.notify("Usage: /bookmark [add|list|remove] ...", "info");
},
});
pi.registerCommand("search", {
description: "Search bookmarks: /search <query>",
handler: async (args, ctx) => {
loadBookmarks(ctx.cwd);
const query = args.trim().toLowerCase();
if (!query) {
ctx.ui.notify("Usage: /search <query>", "warning");
return;
}
const results = bookmarks.filter(
(b) =>
b.name.toLowerCase().includes(query) ||
b.path.toLowerCase().includes(query) ||
(b.note && b.note.toLowerCase().includes(query)),
);
if (results.length === 0) {
ctx.ui.notify(`No bookmarks matching '${query}'.`, "info");
} else {
const list = results
.map((b) => ` - ${b.name} -> ${b.path}${b.line ? `:${b.line}` : ""}${b.note ? ` (${b.note})` : ""}`)
.join("\n");
ctx.ui.notify(`\u{1F50D} Search results for '${query}':\n${list}`, "info");
}
},
});
}

View file

@ -0,0 +1,126 @@
import type { ExtensionAPI } from "@jaeswift/jae-coding-agent";
export default function (pi: ExtensionAPI) {
let checkpointCount = 0;
async function createCheckpoint(ctx: any, label: string): Promise<boolean> {
try {
// Check if in git repo
const statusResult = await pi.exec("git", ["status", "--porcelain"], { cwd: ctx.cwd });
if (statusResult.code !== 0) return false;
// Only checkpoint if there are changes
if (!statusResult.stdout.trim()) return false;
// Stage all and create stash
await pi.exec("git", ["add", "-A"], { cwd: ctx.cwd });
checkpointCount++;
const tag = `jae-checkpoint-${checkpointCount}`;
const result = await pi.exec(
"git",
["stash", "push", "-m", `${tag}: ${label}`, "--include-untracked"],
{ cwd: ctx.cwd },
);
if (result.code === 0 && !result.stdout.includes("No local changes")) {
// Immediately pop to restore working state - the stash entry remains in reflog
await pi.exec("git", ["stash", "pop"], { cwd: ctx.cwd });
return true;
}
return false;
} catch {
return false;
}
}
// Auto-checkpoint on file mutations
pi.on("tool_execution_start", async (event, ctx) => {
const mutatingTools = ["write", "edit"];
if (mutatingTools.includes(event.toolName)) {
await createCheckpoint(ctx, `before ${event.toolName}`);
}
});
// Also checkpoint on bash commands that might modify files
pi.on("tool_call", async (event, ctx) => {
if (event.toolName === "bash") {
const cmd = (event.input as any)?.command || "";
const dangerousPatterns = ["rm ", "mv ", "cp ", "sed ", "chmod ", "chown ", "truncate", "dd "];
if (dangerousPatterns.some((p) => cmd.includes(p))) {
await createCheckpoint(ctx, `before bash: ${cmd.substring(0, 50)}`);
}
}
});
pi.registerCommand("undo", {
description: "Undo last file change by restoring from git stash checkpoint",
handler: async (_args, ctx) => {
try {
// List stashes to find jae checkpoints
const listResult = await pi.exec("git", ["stash", "list"], { cwd: ctx.cwd });
if (listResult.code !== 0) {
ctx.ui.notify("Not in a git repository.", "error");
return;
}
const stashes = listResult.stdout.trim().split("\n").filter(Boolean);
const jaeStashes = stashes.filter((s) => s.includes("jae-checkpoint-"));
if (jaeStashes.length === 0) {
ctx.ui.notify("No JAE checkpoints available to undo.", "warning");
return;
}
// Show what we're undoing
const latest = jaeStashes[0];
const confirmed = await ctx.ui.confirm(
"Undo Checkpoint",
`Restore to: ${latest}\n\nThis will discard current changes and restore the checkpoint.`,
);
if (!confirmed) {
ctx.ui.notify("Undo cancelled.", "info");
return;
}
// Hard reset and apply stash
const stashRef = latest.split(":")[0]; // e.g., "stash@{0}"
await pi.exec("git", ["checkout", "."], { cwd: ctx.cwd });
await pi.exec("git", ["clean", "-fd"], { cwd: ctx.cwd });
const applyResult = await pi.exec("git", ["stash", "apply", stashRef], { cwd: ctx.cwd });
if (applyResult.code === 0) {
ctx.ui.notify(`\u23EA Restored checkpoint: ${latest}`, "info");
} else {
ctx.ui.notify(`Failed to apply stash: ${applyResult.stderr}`, "error");
}
} catch (err) {
ctx.ui.notify(`Undo error: ${err}`, "error");
}
},
});
pi.registerCommand("checkpoints", {
description: "List available JAE checkpoints",
handler: async (_args, ctx) => {
const listResult = await pi.exec("git", ["stash", "list"], { cwd: ctx.cwd });
if (listResult.code !== 0) {
ctx.ui.notify("Not in a git repository.", "error");
return;
}
const stashes = listResult.stdout.trim().split("\n").filter(Boolean);
const jaeStashes = stashes.filter((s) => s.includes("jae-checkpoint-"));
if (jaeStashes.length === 0) {
ctx.ui.notify("No JAE checkpoints found.", "info");
return;
}
ctx.ui.notify(
`\u{1F4CC} JAE Checkpoints (${jaeStashes.length}):\n${jaeStashes.map((s) => ` ${s}`).join("\n")}`,
"info",
);
},
});
}

View file

@ -0,0 +1,104 @@
import type { ExtensionAPI, ExtensionContext } from "@jaeswift/jae-coding-agent";
interface CostState {
inputTokens: number;
outputTokens: number;
cacheReadTokens: number;
cacheWriteTokens: number;
totalCost: number;
budgetLimit: number | null;
turns: number;
}
const COST_PER_1K_INPUT = 0.003;
const COST_PER_1K_OUTPUT = 0.015;
const COST_PER_1K_CACHE_READ = 0.0003;
const COST_PER_1K_CACHE_WRITE = 0.00375;
export default function (pi: ExtensionAPI) {
const state: CostState = {
inputTokens: 0,
outputTokens: 0,
cacheReadTokens: 0,
cacheWriteTokens: 0,
totalCost: 0,
budgetLimit: null,
turns: 0,
};
function recalcCost() {
state.totalCost =
(state.inputTokens / 1000) * COST_PER_1K_INPUT +
(state.outputTokens / 1000) * COST_PER_1K_OUTPUT +
(state.cacheReadTokens / 1000) * COST_PER_1K_CACHE_READ +
(state.cacheWriteTokens / 1000) * COST_PER_1K_CACHE_WRITE;
}
function updateStatus(ctx: ExtensionContext) {
const cost = state.totalCost.toFixed(4);
const budget = state.budgetLimit ? ` / $${state.budgetLimit.toFixed(2)}` : "";
const tokens = ((state.inputTokens + state.outputTokens) / 1000).toFixed(1);
ctx.ui.setStatus("cost-tracker", `\u{1F4B0} $${cost}${budget} | ${tokens}k tok | ${state.turns} turns`);
}
pi.on("turn_start", async (_event, ctx) => {
state.turns++;
updateStatus(ctx);
});
pi.on("agent_end", async (event, ctx) => {
for (const message of event.messages) {
if (message && typeof message === "object" && "usage" in message) {
const u = (message as any).usage;
state.inputTokens += u.input || 0;
state.outputTokens += u.output || 0;
state.cacheReadTokens += u.cacheRead || 0;
state.cacheWriteTokens += u.cacheWrite || 0;
}
}
recalcCost();
updateStatus(ctx);
if (state.budgetLimit && state.totalCost >= state.budgetLimit) {
ctx.ui.notify(
`\u26A0\uFE0F Budget limit of $${state.budgetLimit} reached! Total: $${state.totalCost.toFixed(4)}`,
"warning",
);
}
});
pi.registerCommand("cost", {
description: "Show session cost breakdown",
handler: async (_args, ctx) => {
ctx.ui.notify(
`\u{1F4CA} Cost Report:\n` +
` Input: ${state.inputTokens.toLocaleString()} tokens ($${((state.inputTokens / 1000) * COST_PER_1K_INPUT).toFixed(4)})\n` +
` Output: ${state.outputTokens.toLocaleString()} tokens ($${((state.outputTokens / 1000) * COST_PER_1K_OUTPUT).toFixed(4)})\n` +
` Cache Read: ${state.cacheReadTokens.toLocaleString()} tokens\n` +
` Cache Write: ${state.cacheWriteTokens.toLocaleString()} tokens\n` +
` Total Cost: $${state.totalCost.toFixed(4)}\n` +
` Turns: ${state.turns}`,
"info",
);
},
});
pi.registerCommand("budget", {
description: "Set session budget limit: /budget <amount>",
handler: async (args, ctx) => {
const amount = parseFloat(args || "");
if (isNaN(amount) || amount <= 0) {
ctx.ui.notify(
state.budgetLimit
? `Current budget: $${state.budgetLimit.toFixed(2)} | Spent: $${state.totalCost.toFixed(4)}`
: "No budget set. Usage: /budget 5.00",
"info",
);
} else {
state.budgetLimit = amount;
ctx.ui.notify(`\u{1F4B0} Budget set to $${amount.toFixed(2)}`, "info");
updateStatus(ctx);
}
},
});
}

View file

@ -0,0 +1,109 @@
import type { ExtensionAPI } from "@jaeswift/jae-coding-agent";
interface DashboardState {
gitBranch: string;
modelName: string;
sessionTokens: number;
filesModified: Set<string>;
toolCalls: number;
startTime: number;
}
export default function (pi: ExtensionAPI) {
const state: DashboardState = {
gitBranch: "",
modelName: "",
sessionTokens: 0,
filesModified: new Set(),
toolCalls: 0,
startTime: Date.now(),
};
function formatUptime(): string {
const elapsed = Math.floor((Date.now() - state.startTime) / 1000);
const mins = Math.floor(elapsed / 60);
const secs = elapsed % 60;
return mins > 0 ? `${mins}m${secs}s` : `${secs}s`;
}
function updateDashboard(ctx: any): void {
const tokens = (state.sessionTokens / 1000).toFixed(1);
const branch = state.gitBranch || "n/a";
const model = state.modelName || "unknown";
const files = state.filesModified.size;
const tools = state.toolCalls;
const uptime = formatUptime();
ctx.ui.setFooter(
"dashboard",
`\u{1F4CA} ${model} | \u{1F333} ${branch} | ${tokens}k tok | ${files} files | ${tools} tools | ${uptime}`,
);
}
pi.on("session_start", async (_event, ctx) => {
state.startTime = Date.now();
// Detect git branch
try {
const result = await pi.exec("git", ["branch", "--show-current"], { cwd: ctx.cwd });
if (result.code === 0 && result.stdout.trim()) {
state.gitBranch = result.stdout.trim();
}
} catch { /* not a git repo */ }
// Get model name
if (ctx.model) {
state.modelName = ctx.model.name || ctx.model.id || "unknown";
}
updateDashboard(ctx);
});
pi.on("message_end", async (event, ctx) => {
if (event.message?.usage) {
const u = event.message.usage;
state.sessionTokens += (u.inputTokens || 0) + (u.outputTokens || 0);
}
if (ctx.model) {
state.modelName = ctx.model.name || ctx.model.id || state.modelName;
}
updateDashboard(ctx);
});
pi.on("tool_execution_end", async (event, ctx) => {
state.toolCalls++;
// Track file modifications
if (["write", "edit", "write_file", "edit_file"].includes(event.toolName)) {
const filePath = (event.input as any)?.path || (event.input as any)?.file_path || "";
if (filePath) state.filesModified.add(filePath);
}
// Refresh git branch (might have changed)
try {
const result = await pi.exec("git", ["branch", "--show-current"], { cwd: ctx.cwd });
if (result.code === 0 && result.stdout.trim()) {
state.gitBranch = result.stdout.trim();
}
} catch { /* ignore */ }
updateDashboard(ctx);
});
pi.registerCommand("dashboard", {
description: "Show session dashboard stats",
handler: async (_args, ctx) => {
const uptime = formatUptime();
ctx.ui.notify(
`\u{1F4CA} Session Dashboard:
Model: ${state.modelName || "unknown"}
Git Branch: ${state.gitBranch || "n/a"}
Session Tokens: ${state.sessionTokens.toLocaleString()}
Files Modified: ${state.filesModified.size} (${Array.from(state.filesModified).slice(0, 5).join(", ")}${state.filesModified.size > 5 ? "..." : ""})
Tool Calls: ${state.toolCalls}
Uptime: ${uptime}`,
"info",
);
},
});
}

View file

@ -0,0 +1,134 @@
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");
},
});
}

View file

@ -0,0 +1,48 @@
import type { ExtensionAPI } from "@jaeswift/jae-coding-agent";
import { existsSync, readFileSync, mkdirSync, writeFileSync } from "node:fs";
import { join } from "node:path";
export default function (pi: ExtensionAPI) {
pi.on("before_agent_start", async (_event, ctx) => {
const rulesPath = join(ctx.cwd, ".jae", "rules.md");
if (existsSync(rulesPath)) {
const rules = readFileSync(rulesPath, "utf-8").trim();
if (rules.length > 0) {
return { systemPrompt: `\n\n# Project Rules (.jae/rules.md)\n\n${rules}` };
}
}
});
pi.registerCommand("rules", {
description: "Show, create, or edit project rules (.jae/rules.md)",
handler: async (args, ctx) => {
const jaeDir = join(ctx.cwd, ".jae");
const rulesPath = join(jaeDir, "rules.md");
if (args.trim() === "init") {
if (!existsSync(jaeDir)) mkdirSync(jaeDir, { recursive: true });
if (!existsSync(rulesPath)) {
writeFileSync(
rulesPath,
"# Project Rules\n\nAdd your project-specific rules here.\nThese will be injected into JAE's system prompt.\n",
"utf-8",
);
ctx.ui.notify("Created .jae/rules.md - edit it to set project rules.", "info");
} else {
ctx.ui.notify(".jae/rules.md already exists.", "info");
}
return;
}
if (existsSync(rulesPath)) {
const rules = readFileSync(rulesPath, "utf-8");
ctx.ui.notify(
`\u{1F4CB} Project Rules (${rules.length} chars):\n${rules.substring(0, 500)}${rules.length > 500 ? "..." : ""}`,
"info",
);
} else {
ctx.ui.notify("No .jae/rules.md found. Run /rules init to create one.", "warning");
}
},
});
}

View file

@ -0,0 +1,112 @@
import type { ExtensionAPI } from "@jaeswift/jae-coding-agent";
import { watch, type FSWatcher, readFileSync } from "node:fs";
import { join, relative, extname } from "node:path";
export default function (pi: ExtensionAPI) {
let watcher: FSWatcher | null = null;
let pairMode = false;
let watchedFiles = new Set<string>();
let lastChangeTime = 0;
const DEBOUNCE_MS = 2000;
const CODE_EXTENSIONS = new Set([
".ts", ".tsx", ".js", ".jsx", ".py", ".rs", ".go",
".java", ".c", ".cpp", ".h", ".css", ".scss",
".html", ".vue", ".svelte", ".rb", ".php",
".json", ".yaml", ".yml", ".toml", ".md",
]);
const IGNORE_PATTERNS = [
"node_modules", ".git", "dist", "build", ".jae",
".next", ".cache", "__pycache__", "target",
];
function shouldWatch(filepath: string): boolean {
if (IGNORE_PATTERNS.some((p) => filepath.includes(p))) return false;
return CODE_EXTENSIONS.has(extname(filepath));
}
function startWatching(cwd: string, ctx: any): void {
if (watcher) {
watcher.close();
}
try {
watcher = watch(cwd, { recursive: true }, (eventType, filename) => {
if (!filename || !pairMode) return;
const filepath = join(cwd, filename);
if (!shouldWatch(filepath)) return;
const now = Date.now();
if (now - lastChangeTime < DEBOUNCE_MS) return;
lastChangeTime = now;
const relPath = relative(cwd, filepath);
watchedFiles.add(relPath);
try {
const content = readFileSync(filepath, "utf-8");
const lines = content.split("\n").length;
ctx.ui.notify(
`\u{1F4DD} File changed: ${relPath} (${lines} lines, ${eventType})\nJAE is watching in pair mode.`,
"info",
);
} catch {
ctx.ui.notify(`\u{1F4DD} File ${eventType}: ${relPath}`, "info");
}
});
ctx.ui.notify(
`\u{1F91D} Pair programming mode started!\nWatching: ${cwd}\nJAE will observe your changes and provide feedback.`,
"info",
);
} catch (err) {
ctx.ui.notify(`Failed to start file watcher: ${err}`, "error");
}
}
function stopWatching(ctx: any): void {
if (watcher) {
watcher.close();
watcher = null;
}
const filesCount = watchedFiles.size;
watchedFiles.clear();
ctx.ui.notify(
`\u{1F91D} Pair programming mode stopped. ${filesCount} files were modified during session.`,
"info",
);
}
pi.on("session_end", async (_event, _ctx) => {
if (watcher) {
watcher.close();
watcher = null;
pairMode = false;
}
});
pi.on("before_agent_start", async (_event, _ctx) => {
if (pairMode && watchedFiles.size > 0) {
const filesList = Array.from(watchedFiles).slice(0, 10).join(", ");
return {
systemPrompt: `\n\n# Pair Programming Mode Active\nRecently modified files: ${filesList}\nProvide constructive feedback on changes. Suggest improvements, catch bugs, and explain patterns.\n`,
};
}
});
pi.registerCommand("pair", {
description: "Toggle pair programming mode - JAE watches and comments on your file changes",
handler: async (_args, ctx) => {
pairMode = !pairMode;
if (pairMode) {
startWatching(ctx.cwd, ctx);
ctx.ui.setFooter("pair", "\u{1F91D} Pair Mode");
} else {
stopWatching(ctx);
ctx.ui.setFooter("pair", undefined);
}
},
});
}

View file

@ -0,0 +1,112 @@
import type { ExtensionAPI } from "@jaeswift/jae-coding-agent";
import { Type } from "@sinclair/typebox";
export default function (pi: ExtensionAPI) {
async function reviewDiff(diff: string, ctx: any): Promise<string> {
if (!diff.trim()) return "No diff content to review.";
const lines = diff.split("\n");
const stats = {
filesChanged: 0,
additions: 0,
deletions: 0,
files: [] as string[],
};
for (const line of lines) {
if (line.startsWith("+++ b/")) {
stats.filesChanged++;
stats.files.push(line.replace("+++ b/", ""));
} else if (line.startsWith("+") && !line.startsWith("+++")) {
stats.additions++;
} else if (line.startsWith("-") && !line.startsWith("---")) {
stats.deletions++;
}
}
return [
`## PR Review Summary`,
``,
`**Files changed:** ${stats.filesChanged}`,
`**Additions:** +${stats.additions}`,
`**Deletions:** -${stats.deletions}`,
``,
`### Files:`,
...stats.files.map((f) => `- ${f}`),
``,
`### Diff Preview (first 200 lines):`,
"```diff",
lines.slice(0, 200).join("\n"),
"```",
``,
`> Full review requires LLM analysis. Send this diff as context to JAE for detailed review.`,
].join("\n");
}
pi.registerTool({
name: "pr_review",
label: "PR Review",
description: "Review a pull request or git diff. Provide a branch name, PR URL, or commit range.",
parameters: Type.Object({
target: Type.String({ description: "Branch name, PR URL, or commit range (e.g., 'main..feature', 'origin/main')" }),
}),
execute: async (args, ctx) => {
const { target } = args;
let diff = "";
// Try as branch comparison
const diffResult = await pi.exec(
"git",
["diff", target],
{ cwd: ctx.cwd },
);
if (diffResult.code === 0 && diffResult.stdout.trim()) {
diff = diffResult.stdout;
} else {
// Try as range
const rangeResult = await pi.exec(
"git",
["diff", `${target}`],
{ cwd: ctx.cwd },
);
if (rangeResult.code === 0) {
diff = rangeResult.stdout;
} else {
return { error: `Could not get diff for: ${target}. Error: ${diffResult.stderr || rangeResult.stderr}` };
}
}
const review = await reviewDiff(diff, ctx);
return { output: review };
},
});
pi.registerCommand("pr-review", {
description: "Review a PR or branch diff: /pr-review <branch|range>",
handler: async (args, ctx) => {
const target = args.trim();
if (!target) {
ctx.ui.notify("Usage: /pr-review <branch|commit-range>\nExamples:\n /pr-review main..feature\n /pr-review origin/main", "info");
return;
}
ctx.ui.notify(`\u{1F50D} Fetching diff for: ${target}...`, "info");
const diffResult = await pi.exec("git", ["diff", target], { cwd: ctx.cwd });
if (diffResult.code !== 0) {
ctx.ui.notify(`Failed to get diff: ${diffResult.stderr}`, "error");
return;
}
if (!diffResult.stdout.trim()) {
ctx.ui.notify("No differences found.", "info");
return;
}
const review = await reviewDiff(diffResult.stdout, ctx);
ctx.ui.notify(review, "info");
},
});
}

View file

@ -0,0 +1,139 @@
import type { ExtensionAPI } from "@jaeswift/jae-coding-agent";
import { existsSync, readFileSync, mkdirSync, writeFileSync } from "node:fs";
import { join } from "node:path";
export default function (pi: ExtensionAPI) {
pi.on("before_agent_start", async (_event, ctx) => {
const dnaPath = join(ctx.cwd, ".jae", "project-dna.md");
if (existsSync(dnaPath)) {
const dna = readFileSync(dnaPath, "utf-8").trim();
if (dna.length > 0) {
return { systemPrompt: `\n\n# Project DNA (auto-generated fingerprint)\n\n${dna}` };
}
}
});
pi.on("session_start", async (_event, ctx) => {
const dnaPath = join(ctx.cwd, ".jae", "project-dna.md");
if (!existsSync(dnaPath)) {
ctx.ui.notify("\u{1F9EC} No project DNA found. Run /dna to auto-analyze your project.", "info");
}
});
pi.registerCommand("dna", {
description: "Auto-analyze project and generate .jae/project-dna.md fingerprint",
handler: async (args, ctx) => {
const cwd = ctx.cwd;
const jaeDir = join(cwd, ".jae");
const dnaPath = join(jaeDir, "project-dna.md");
if (args.trim() === "show" && existsSync(dnaPath)) {
const dna = readFileSync(dnaPath, "utf-8");
ctx.ui.notify(`\u{1F9EC} Project DNA:\n${dna.substring(0, 1500)}`, "info");
return;
}
ctx.ui.notify("\u{1F50D} Analyzing project...", "info");
const sections: string[] = ["# Project DNA\n"];
// Tech stack detection
const checks = [
{ file: "package.json", label: "Node.js" },
{ file: "Cargo.toml", label: "Rust" },
{ file: "go.mod", label: "Go" },
{ file: "pyproject.toml", label: "Python (pyproject)" },
{ file: "requirements.txt", label: "Python (pip)" },
{ file: "Gemfile", label: "Ruby" },
{ file: "pom.xml", label: "Java (Maven)" },
{ file: "build.gradle", label: "Java/Kotlin (Gradle)" },
{ file: "docker-compose.yml", label: "Docker Compose" },
{ file: "Dockerfile", label: "Docker" },
{ file: ".github/workflows", label: "GitHub Actions" },
{ file: "tsconfig.json", label: "TypeScript" },
{ file: ".eslintrc.json", label: "ESLint" },
{ file: "vitest.config.ts", label: "Vitest" },
{ file: "jest.config.js", label: "Jest" },
{ file: "tailwind.config.js", label: "Tailwind CSS" },
{ file: "next.config.js", label: "Next.js" },
{ file: "vite.config.ts", label: "Vite" },
];
const detected: string[] = [];
for (const c of checks) {
if (existsSync(join(cwd, c.file))) detected.push(c.label);
}
sections.push(`## Tech Stack\n${detected.length > 0 ? detected.map((d) => `- ${d}`).join("\n") : "- Unknown"}\n`);
// Package.json details
const pkgPath = join(cwd, "package.json");
if (existsSync(pkgPath)) {
try {
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
sections.push(
`## Package Info\n- Name: ${pkg.name || "unknown"}\n- Version: ${pkg.version || "unknown"}\n- Description: ${pkg.description || "none"}\n`,
);
if (pkg.dependencies) {
const deps = Object.keys(pkg.dependencies).slice(0, 20);
sections.push(`## Dependencies (top 20)\n${deps.map((d) => `- ${d}`).join("\n")}\n`);
}
if (pkg.scripts) {
const scripts = Object.entries(pkg.scripts).slice(0, 15) as [string, string][];
sections.push(`## Scripts\n${scripts.map(([k, v]) => `- \`${k}\`: ${v}`).join("\n")}\n`);
}
} catch {
/* ignore parse errors */
}
}
// Git info
try {
const gitResult = await pi.exec("git", ["log", "--oneline", "-10"], { cwd });
if (gitResult.code === 0 && gitResult.stdout.trim()) {
sections.push(`## Recent Git History\n\`\`\`\n${gitResult.stdout.trim()}\n\`\`\`\n`);
}
const branchResult = await pi.exec("git", ["branch", "--show-current"], { cwd });
if (branchResult.code === 0 && branchResult.stdout.trim()) {
sections.push(`## Current Branch: ${branchResult.stdout.trim()}\n`);
}
} catch {
/* not a git repo */
}
// File stats
try {
const findResult = await pi.exec(
"find",
[".", "-maxdepth", "3", "-type", "f", "-not", "-path", "*/node_modules/*", "-not", "-path", "*/.git/*", "-not", "-path", "*/dist/*", "-not", "-path", "*/.jae/*"],
{ cwd },
);
if (findResult.code === 0) {
const files = findResult.stdout.trim().split("\n").filter(Boolean);
sections.push(`## File Count: ${files.length}\n`);
const extCounts: Record<string, number> = {};
for (const f of files) {
const ext = f.includes(".") ? (f.split(".").pop() || "none") : "(no ext)";
extCounts[ext] = (extCounts[ext] || 0) + 1;
}
const sorted = Object.entries(extCounts)
.sort((a, b) => b[1] - a[1])
.slice(0, 15);
sections.push(`## File Types\n${sorted.map(([ext, count]) => `- .${ext}: ${count}`).join("\n")}\n`);
}
} catch {
/* ignore */
}
sections.push(`\n---\nGenerated: ${new Date().toISOString()}\n`);
const dnaContent = sections.join("\n");
if (!existsSync(jaeDir)) mkdirSync(jaeDir, { recursive: true });
writeFileSync(dnaPath, dnaContent, "utf-8");
ctx.ui.notify(
`\u{1F9EC} Project DNA generated (${dnaContent.length} chars). Saved to .jae/project-dna.md`,
"info",
);
},
});
}

View file

@ -0,0 +1,127 @@
import type { ExtensionAPI } from "@jaeswift/jae-coding-agent";
import { writeFileSync } from "node:fs";
import { join } from "node:path";
interface ReplayEvent {
time: number;
type: string;
text: string;
}
export default function (pi: ExtensionAPI) {
let recording = false;
let events: ReplayEvent[] = [];
let startTime = 0;
pi.on("message_end", async (event, ctx) => {
if (!recording) return;
const msg = event.message;
if (!msg) return;
let text = "";
if ("content" in msg && typeof msg.content === "string") {
text = msg.content;
} else if ("content" in msg && Array.isArray(msg.content)) {
text = (msg.content as any[])
.filter((b: any) => b.type === "text")
.map((b: any) => b.text)
.join("");
}
if (text) {
events.push({
time: (Date.now() - startTime) / 1000,
type: (msg as any).role || "unknown",
text,
});
}
});
pi.on("tool_execution_end", async (event, _ctx) => {
if (!recording) return;
const resultText =
typeof event.result === "string"
? event.result
: JSON.stringify(event.result).substring(0, 500);
events.push({
time: (Date.now() - startTime) / 1000,
type: `tool:${event.toolName}`,
text: resultText,
});
});
pi.registerCommand("replay", {
description: "Session replay: /replay start | /replay stop | /replay export [file.cast]",
handler: async (args, ctx) => {
const parts = args.trim().split(/\s+/);
const sub = parts[0] || "status";
if (sub === "start") {
if (recording) {
ctx.ui.notify("Already recording.", "warning");
return;
}
recording = true;
events = [];
startTime = Date.now();
ctx.ui.setStatus("replay", "\u{1F534} REC");
ctx.ui.notify("\u{1F3AC} Replay recording started.", "info");
return;
}
if (sub === "stop") {
if (!recording) {
ctx.ui.notify("Not recording.", "warning");
return;
}
recording = false;
ctx.ui.setStatus("replay", undefined);
ctx.ui.notify(`\u{1F3AC} Recording stopped. ${events.length} events captured. Use /replay export to save.`, "info");
return;
}
if (sub === "export") {
if (events.length === 0) {
ctx.ui.notify("No events recorded. Start a recording first.", "warning");
return;
}
const filename = parts[1] || `jae-replay-${Date.now()}.cast`;
const outPath = join(ctx.cwd, filename);
// asciicast v2 format
const header = JSON.stringify({
version: 2,
width: 120,
height: 40,
timestamp: Math.floor(startTime / 1000),
title: "JAE Session Replay",
env: { TERM: "xterm-256color" },
});
const lines = [header];
for (const ev of events) {
const prefix = ev.type === "assistant" ? "\x1b[36m" : ev.type.startsWith("tool:") ? "\x1b[33m" : "\x1b[37m";
const label = ev.type === "assistant" ? "JAE" : ev.type === "user" ? "YOU" : ev.type;
const displayText = `${prefix}[${label}]\x1b[0m ${ev.text}\r\n`;
lines.push(JSON.stringify([ev.time, "o", displayText]));
}
writeFileSync(outPath, lines.join("\n") + "\n", "utf-8");
ctx.ui.notify(`\u{1F3AC} Exported ${events.length} events to ${outPath}\nPlay with: asciinema play ${filename}`, "info");
return;
}
if (sub === "status") {
ctx.ui.notify(
recording
? `\u{1F534} Recording in progress: ${events.length} events (${((Date.now() - startTime) / 1000).toFixed(0)}s)`
: `\u{1F3AC} Not recording. ${events.length} events in buffer.`,
"info",
);
return;
}
ctx.ui.notify("Usage: /replay start | stop | export [file.cast] | status", "info");
},
});
}

View file

@ -0,0 +1,47 @@
import type { ExtensionAPI } from "@jaeswift/jae-coding-agent";
export default function (pi: ExtensionAPI) {
pi.on("before_agent_start", async (_event, _ctx) => {
return {
systemPrompt: `
# Screenshot & Image Analysis
When the user pastes or attaches an image (screenshot, diagram, mockup, error screenshot):
1. Analyze the visual content thoroughly
2. If it's a UI screenshot: describe layout, identify components, suggest improvements
3. If it's an error screenshot: read the error message, diagnose the issue, suggest fixes
4. If it's a design mockup: break down into implementable components
5. If it's a diagram: interpret the architecture/flow and relate to the codebase
6. If it's code in an image: transcribe it accurately and analyze
Always acknowledge what you see in the image before providing analysis.
`,
};
});
pi.on("input", async (event, ctx) => {
// Check if input contains image attachments
const input = event as any;
if (input.images && input.images.length > 0) {
ctx.ui.notify(
`\u{1F4F7} ${input.images.length} image(s) detected. JAE will analyze with vision capabilities.`,
"info",
);
}
});
pi.registerCommand("screenshot", {
description: "Tips for using screenshot context with JAE",
handler: async (_args, ctx) => {
ctx.ui.notify(
`\u{1F4F7} Screenshot Context Tips:
- Paste screenshots directly into the chat
- JAE will automatically analyze images with vision
- Supported: UI screenshots, error messages, diagrams, mockups
- For best results, crop to the relevant area
- You can paste multiple images in one message`,
"info",
);
},
});
}

View file

@ -0,0 +1,189 @@
import type { ExtensionAPI } from "@jaeswift/jae-coding-agent";
import { existsSync, writeFileSync, mkdirSync, readFileSync, readdirSync } from "node:fs";
import { join } from "node:path";
import { Type } from "@sinclair/typebox";
const SKILLS_API = "https://skills.jaeswift.xyz/api/skills";
interface SkillInfo {
name: string;
description: string;
version: string;
author: string;
}
export default function (pi: ExtensionAPI) {
async function fetchJSON(url: string): Promise<any> {
const result = await pi.exec("curl", ["-s", "-f", url]);
if (result.code !== 0) {
throw new Error(`HTTP request failed: ${result.stderr}`);
}
return JSON.parse(result.stdout);
}
function getSkillsDir(ctx: any): string {
const dir = join(ctx.agentDir, "skills");
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
return dir;
}
function listLocalSkills(ctx: any): string[] {
const dir = getSkillsDir(ctx);
try {
return readdirSync(dir).filter((f) => f.endsWith(".md") || existsSync(join(dir, f, "SKILL.md")));
} catch {
return [];
}
}
// /search-skills command
pi.registerCommand("search-skills", {
description: "Search the JAE skill marketplace: /search-skills <query>",
handler: async (args, ctx) => {
const query = args.trim();
if (!query) {
ctx.ui.notify("Usage: /search-skills <query>", "warning");
return;
}
ctx.ui.notify(`\u{1F50D} Searching skills for: ${query}...`, "info");
try {
const skills = await fetchJSON(`${SKILLS_API}/search?q=${encodeURIComponent(query)}`);
if (!skills || skills.length === 0) {
ctx.ui.notify(`No skills found matching '${query}'.`, "info");
return;
}
const list = skills
.map((s: SkillInfo) => ` - ${s.name} (v${s.version}) by ${s.author}\n ${s.description}`)
.join("\n");
ctx.ui.notify(`\u{1F3EA} Skills matching '${query}':\n${list}`, "info");
} catch (err: any) {
ctx.ui.notify(`Failed to search skills: ${err.message}\nAPI: ${SKILLS_API}`, "error");
}
},
});
// /install-skill command
pi.registerCommand("install-skill", {
description: "Install a skill from the marketplace: /install-skill <name>",
handler: async (args, ctx) => {
const name = args.trim();
if (!name) {
ctx.ui.notify("Usage: /install-skill <name>", "warning");
return;
}
ctx.ui.notify(`\u{1F4E6} Installing skill: ${name}...`, "info");
try {
const skill = await fetchJSON(`${SKILLS_API}/${encodeURIComponent(name)}`);
if (!skill || !skill.content) {
ctx.ui.notify(`Skill '${name}' not found or has no content.`, "error");
return;
}
const skillsDir = getSkillsDir(ctx);
const skillDir = join(skillsDir, name);
if (!existsSync(skillDir)) mkdirSync(skillDir, { recursive: true });
writeFileSync(join(skillDir, "SKILL.md"), skill.content, "utf-8");
// Write any additional files
if (skill.files && typeof skill.files === "object") {
for (const [filename, content] of Object.entries(skill.files)) {
writeFileSync(join(skillDir, filename), content as string, "utf-8");
}
}
ctx.ui.notify(
`\u2705 Skill '${name}' installed to ${skillDir}\nRestart JAE to activate.`,
"info",
);
} catch (err: any) {
ctx.ui.notify(`Failed to install skill: ${err.message}`, "error");
}
},
});
// /publish-skill command
pi.registerCommand("publish-skill", {
description: "Publish a local skill to the marketplace: /publish-skill <name>",
handler: async (args, ctx) => {
const name = args.trim();
if (!name) {
ctx.ui.notify("Usage: /publish-skill <skill-dir-name>", "warning");
return;
}
const skillsDir = getSkillsDir(ctx);
const skillDir = join(skillsDir, name);
const skillMd = join(skillDir, "SKILL.md");
if (!existsSync(skillMd)) {
ctx.ui.notify(`Skill '${name}' not found locally at ${skillDir}`, "error");
return;
}
const content = readFileSync(skillMd, "utf-8");
// Collect extra files
const files: Record<string, string> = {};
try {
for (const f of readdirSync(skillDir)) {
if (f !== "SKILL.md") {
files[f] = readFileSync(join(skillDir, f), "utf-8");
}
}
} catch { /* ignore */ }
ctx.ui.notify(`\u{1F4E4} Publishing skill: ${name}...`, "info");
try {
const payload = JSON.stringify({ name, content, files });
const result = await pi.exec(
"curl",
["-s", "-f", "-X", "POST", "-H", "Content-Type: application/json", "-d", payload, SKILLS_API],
);
if (result.code === 0) {
ctx.ui.notify(`\u2705 Skill '${name}' published to marketplace!`, "info");
} else {
ctx.ui.notify(`Publish failed: ${result.stderr}`, "error");
}
} catch (err: any) {
ctx.ui.notify(`Publish error: ${err.message}`, "error");
}
},
});
// /skills command - list local skills
pi.registerCommand("skills", {
description: "List locally installed skills",
handler: async (_args, ctx) => {
const local = listLocalSkills(ctx);
if (local.length === 0) {
ctx.ui.notify("No skills installed. Use /search-skills and /install-skill to get started.", "info");
return;
}
ctx.ui.notify(`\u{1F9E0} Installed Skills (${local.length}):\n${local.map((s) => ` - ${s}`).join("\n")}`, "info");
},
});
// LLM tool for skill search
pi.registerTool({
name: "search_skills",
label: "Search Skills",
description: "Search the JAE skill marketplace for installable skills",
parameters: Type.Object({
query: Type.String({ description: "Search query for skills" }),
}),
execute: async (args, _ctx) => {
try {
const skills = await fetchJSON(`${SKILLS_API}/search?q=${encodeURIComponent(args.query)}`);
return { output: JSON.stringify(skills, null, 2) };
} catch (err: any) {
return { error: `Skill search failed: ${err.message}` };
}
},
});
}

147
default-extensions/swarm.ts Normal file
View file

@ -0,0 +1,147 @@
import type { ExtensionAPI } from "@jaeswift/jae-coding-agent";
import { execSync, spawn } from "node:child_process";
import { writeFileSync, readFileSync, mkdirSync, existsSync } from "node:fs";
import { join } from "node:path";
import { Type } from "@sinclair/typebox";
interface SwarmTask {
id: number;
description: string;
status: "pending" | "running" | "done" | "error";
result: string;
}
export default function (pi: ExtensionAPI) {
let taskCounter = 0;
async function runSubtask(
description: string,
cwd: string,
): Promise<{ success: boolean; output: string }> {
return new Promise((resolve) => {
const timeout = 120_000; // 2 minutes max per subtask
let output = "";
let resolved = false;
try {
const child = spawn(
"jae",
["-p", description],
{
cwd,
stdio: ["ignore", "pipe", "pipe"],
timeout,
env: { ...process.env },
},
);
child.stdout?.on("data", (data: Buffer) => {
output += data.toString();
});
child.stderr?.on("data", (data: Buffer) => {
output += data.toString();
});
child.on("close", (code) => {
if (!resolved) {
resolved = true;
resolve({ success: code === 0, output: output.trim() || "(no output)" });
}
});
child.on("error", (err) => {
if (!resolved) {
resolved = true;
resolve({ success: false, output: `Process error: ${err.message}` });
}
});
setTimeout(() => {
if (!resolved) {
resolved = true;
try { child.kill("SIGTERM"); } catch {}
resolve({ success: false, output: output.trim() + "\n(timed out)" });
}
}, timeout);
} catch (err: any) {
resolve({ success: false, output: `Spawn error: ${err.message}` });
}
});
}
pi.registerTool({
name: "swarm_task",
label: "Swarm Task",
description:
"Split a complex task into subtasks and run them in parallel using multiple JAE instances. Provide the main task and a list of subtask descriptions.",
parameters: Type.Object({
task: Type.String({ description: "Main task description" }),
subtasks: Type.Array(Type.String(), {
description: "List of subtask descriptions to run in parallel",
}),
}),
execute: async (args, ctx) => {
const { task, subtasks } = args;
if (!subtasks || subtasks.length === 0) {
return { error: "No subtasks provided" };
}
const tasks: SwarmTask[] = subtasks.map((desc: string) => ({
id: ++taskCounter,
description: desc,
status: "pending" as const,
result: "",
}));
ctx.ui.notify(
`\u{1F41D} Swarm: Launching ${tasks.length} subtasks for: ${task}`,
"info",
);
// Run all in parallel
const promises = tasks.map(async (t) => {
t.status = "running";
const res = await runSubtask(t.description, ctx.cwd);
t.status = res.success ? "done" : "error";
t.result = res.output;
return t;
});
const results = await Promise.all(promises);
const summary = results
.map(
(t) =>
`### Subtask #${t.id}: ${t.status === "done" ? "\u2705" : "\u274C"} ${t.description}\n${t.result.substring(0, 500)}`,
)
.join("\n\n");
const successCount = results.filter((t) => t.status === "done").length;
return {
output: `# Swarm Results\n\nTask: ${task}\nCompleted: ${successCount}/${tasks.length}\n\n${summary}`,
};
},
});
pi.registerCommand("swarm", {
description:
"Run parallel subtasks: /swarm <task description> (JAE will decompose into subtasks)",
handler: async (args, ctx) => {
const task = args.trim();
if (!task) {
ctx.ui.notify(
"Usage: /swarm <task description>\n\nJAE will decompose the task into parallel subtasks and run them simultaneously.",
"info",
);
return;
}
ctx.ui.notify(
`\u{1F41D} Swarm mode: Submit this task to JAE and it will use the swarm_task tool to parallelize it.\n\nTask: ${task}`,
"info",
);
},
});
}

View file

@ -0,0 +1,38 @@
import type { ExtensionAPI } from "@jaeswift/jae-coding-agent";
const TEACH_PROMPT = `
# Teach Mode Active
You are in TEACH MODE. For every action you take:
1. Explain WHY you are making this decision (not just what)
2. Describe alternative approaches you considered and why you rejected them
3. Point out patterns, best practices, and potential pitfalls
4. When writing code, add educational comments explaining non-obvious logic
5. After completing a task, provide a brief summary of what was learned
6. Use analogies and examples to clarify complex concepts
7. Highlight any trade-offs being made (performance vs readability, etc.)
`;
export default function (pi: ExtensionAPI) {
let teachModeOn = false;
pi.on("before_agent_start", async (_event, _ctx) => {
if (teachModeOn) {
return { systemPrompt: TEACH_PROMPT };
}
});
pi.registerCommand("teach", {
description: "Toggle teach mode - JAE explains every decision in detail",
handler: async (_args, ctx) => {
teachModeOn = !teachModeOn;
ctx.ui.setStatus("teach-mode", teachModeOn ? "\u{1F393} Teach Mode" : undefined);
ctx.ui.notify(
teachModeOn
? "\u{1F393} Teach Mode ON - JAE will explain every decision in detail."
: "\u{1F393} Teach Mode OFF - Back to normal operation.",
"info",
);
},
});
}

View file

@ -0,0 +1,84 @@
import type { ExtensionAPI } from "@jaeswift/jae-coding-agent";
export default function (pi: ExtensionAPI) {
interface WidgetState {
activeTool: string;
lastToolDuration: number;
errorsCount: number;
warningsCount: number;
lastActivity: string;
}
const state: WidgetState = {
activeTool: "none",
lastToolDuration: 0,
errorsCount: 0,
warningsCount: 0,
lastActivity: "idle",
};
let toolStartTime = 0;
function renderWidget(): string {
const lines = [
`\u{250C}\u{2500} JAE Status \u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2510}`,
`\u{2502} Activity: ${state.lastActivity.padEnd(12)} \u{2502}`,
`\u{2502} Tool: ${state.activeTool.padEnd(12)} \u{2502}`,
`\u{2502} Last: ${(state.lastToolDuration + "ms").padEnd(12)} \u{2502}`,
`\u{2502} Errors: ${String(state.errorsCount).padEnd(12)} \u{2502}`,
`\u{2514}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2518}`,
];
return lines.join("\n");
}
function updateWidget(ctx: any): void {
ctx.ui.setFooter("widget-demo", `\u{1F4DF} ${state.lastActivity} | Tool: ${state.activeTool} | Errs: ${state.errorsCount}`);
}
pi.on("tool_execution_start", async (event, ctx) => {
toolStartTime = Date.now();
state.activeTool = event.toolName || "unknown";
state.lastActivity = "running";
updateWidget(ctx);
});
pi.on("tool_execution_end", async (event, ctx) => {
state.lastToolDuration = Date.now() - toolStartTime;
state.activeTool = "none";
state.lastActivity = "idle";
// Check for errors in result
const result = typeof event.result === "string" ? event.result : JSON.stringify(event.result);
if (result.toLowerCase().includes("error")) {
state.errorsCount++;
}
updateWidget(ctx);
});
pi.on("message_end", async (event, ctx) => {
state.lastActivity = "thinking";
updateWidget(ctx);
});
pi.on("turn_start", async (_event, ctx) => {
state.lastActivity = "processing";
updateWidget(ctx);
});
pi.on("turn_end", async (_event, ctx) => {
state.lastActivity = "idle";
updateWidget(ctx);
});
pi.registerCommand("widget", {
description: "Show the JAE status widget demo",
handler: async (_args, ctx) => {
const widget = renderWidget();
ctx.ui.notify(
`\u{1F4DF} Widget API Demo:\n\n${widget}\n\nThis demonstrates how extensions can render live TUI widgets.\nThe footer bar shows real-time status updates.\n\nWidget State:\n Active Tool: ${state.activeTool}\n Last Duration: ${state.lastToolDuration}ms\n Errors: ${state.errorsCount}\n Activity: ${state.lastActivity}`,
"info",
);
},
});
}