Initial: pi-skill — 68 skills, 43 extensions, 11 themes for Pi
This commit is contained in:
179
extensions/send-email.ts
Normal file
179
extensions/send-email.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
// ABOUTME: Agent email sending extension — enables agents to send emails via AgentMail through Commander.
|
||||
// ABOUTME: Registers a send_email tool that proxies to commander_agentmail for reports, briefings, and custom emails.
|
||||
|
||||
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import { Text } from "@mariozechner/pi-tui";
|
||||
|
||||
// ── Types ────────────────────────────────────────────────────────────
|
||||
|
||||
interface SendEmailParams {
|
||||
to?: string;
|
||||
subject?: string;
|
||||
body?: string;
|
||||
html?: string;
|
||||
type?: "generic" | "report" | "briefing";
|
||||
report_name?: string;
|
||||
format?: "markdown" | "html" | "text";
|
||||
}
|
||||
|
||||
// ── Tool Registration ────────────────────────────────────────────────
|
||||
|
||||
export default function (pi: ExtensionAPI) {
|
||||
pi.registerTool({
|
||||
name: "send_email",
|
||||
label: "Send Email",
|
||||
description: [
|
||||
"Send an email via AgentMail through the Commander assistant.",
|
||||
"Uses the same email system as Commander reports and briefings.",
|
||||
"Default recipient: ruizrica2@gmail.com",
|
||||
"",
|
||||
"Three modes:",
|
||||
" generic — send a custom email with subject and body/content",
|
||||
" report — send a formatted report (markdown auto-converted to styled HTML)",
|
||||
" briefing — send a morning briefing email",
|
||||
"",
|
||||
"Content supports markdown (auto-converted to HTML), raw HTML, or plain text.",
|
||||
"",
|
||||
"Examples:",
|
||||
' { type: "report", report_name: "Feature Complete", body: "## Summary\\nAdded auth..." }',
|
||||
' { type: "generic", subject: "Build Results", body: "All 42 tests passed." }',
|
||||
' { type: "generic", to: "team@example.com", subject: "Deploy Done", body: "v2.1 is live" }',
|
||||
].join("\n"),
|
||||
parameters: Type.Object({
|
||||
to: Type.Optional(Type.String({ description: "Recipient email address. Default: ruizrica2@gmail.com" })),
|
||||
subject: Type.Optional(Type.String({ description: "Email subject line (required for generic, auto-generated for report/briefing)." })),
|
||||
body: Type.Optional(Type.String({ description: "Email body content — markdown (default), HTML, or plain text." })),
|
||||
html: Type.Optional(Type.String({ description: "Raw HTML email body (overrides body)." })),
|
||||
type: Type.Optional(Type.String({ description: "Email type: 'generic' (default), 'report', or 'briefing'." })),
|
||||
report_name: Type.Optional(Type.String({ description: "Report name for subject line (for report type)." })),
|
||||
format: Type.Optional(Type.String({ description: "Content format: 'markdown' (default), 'html', 'text'." })),
|
||||
}),
|
||||
|
||||
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
||||
const p = params as SendEmailParams;
|
||||
const emailType = (p.type || "generic").toLowerCase();
|
||||
|
||||
// ── Try to call commander_agentmail via the MCP client ──
|
||||
const g = globalThis as any;
|
||||
|
||||
// Check if Commander is available
|
||||
const gate = g.__piCommanderGate;
|
||||
if (!gate || gate.status !== "available") {
|
||||
return {
|
||||
content: [{ type: "text" as const, text: "Email sending failed: Commander is not connected. The send_email tool requires Commander with AgentMail configured." }],
|
||||
details: { success: false, error: "commander_not_available" },
|
||||
};
|
||||
}
|
||||
|
||||
// Build the commander_agentmail call based on email type
|
||||
let agentmailParams: Record<string, string | undefined>;
|
||||
|
||||
if (emailType === "report") {
|
||||
if (!p.body && !p.html) {
|
||||
return {
|
||||
content: [{ type: "text" as const, text: "Email sending failed: 'body' content is required for report emails." }],
|
||||
details: { success: false, error: "missing_content" },
|
||||
};
|
||||
}
|
||||
agentmailParams = {
|
||||
operation: "send:report",
|
||||
report_name: p.report_name || p.subject || "Completion Report",
|
||||
content: p.html || p.body,
|
||||
format: p.html ? "html" : (p.format || "markdown"),
|
||||
};
|
||||
if (p.to) agentmailParams.to = p.to;
|
||||
} else if (emailType === "briefing") {
|
||||
if (!p.body) {
|
||||
return {
|
||||
content: [{ type: "text" as const, text: "Email sending failed: 'body' content is required for briefing emails." }],
|
||||
details: { success: false, error: "missing_content" },
|
||||
};
|
||||
}
|
||||
agentmailParams = {
|
||||
operation: "send:briefing",
|
||||
content: p.body,
|
||||
};
|
||||
if (p.to) agentmailParams.to = p.to;
|
||||
} else {
|
||||
// Generic email
|
||||
if (!p.subject) {
|
||||
return {
|
||||
content: [{ type: "text" as const, text: "Email sending failed: 'subject' is required for generic emails." }],
|
||||
details: { success: false, error: "missing_subject" },
|
||||
};
|
||||
}
|
||||
if (!p.body && !p.html) {
|
||||
return {
|
||||
content: [{ type: "text" as const, text: "Email sending failed: 'body' or 'html' is required for generic emails." }],
|
||||
details: { success: false, error: "missing_body" },
|
||||
};
|
||||
}
|
||||
agentmailParams = {
|
||||
operation: "send:custom",
|
||||
subject: p.subject,
|
||||
content: p.html || p.body,
|
||||
format: p.html ? "html" : (p.format || "markdown"),
|
||||
};
|
||||
if (p.to) agentmailParams.to = p.to;
|
||||
}
|
||||
|
||||
// Call commander_agentmail through the tool system
|
||||
try {
|
||||
// Use ctx.callTool if available, otherwise fall back to finding the tool
|
||||
if (ctx && typeof (ctx as any).callTool === "function") {
|
||||
const result = await (ctx as any).callTool("commander_agentmail", agentmailParams);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Fallback: call via the registered Pi tool directly
|
||||
const piGlobal = g.__piInstance || g.__pi;
|
||||
if (piGlobal && typeof piGlobal.callTool === "function") {
|
||||
const result = await piGlobal.callTool("commander_agentmail", agentmailParams);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Last resort: use the MCP client directly
|
||||
const McpClientModule = await import("./lib/mcp-client.ts");
|
||||
const serverPath = "/Users/ricardo/Workshop/Github-Work/commander/services/commander-mcp/dist/server.js";
|
||||
const client = new McpClientModule.McpClient(serverPath, {
|
||||
COMMANDER_WS_URL: process.env.COMMANDER_WS_URL || "ws://localhost:9002",
|
||||
AGENTMAIL_API_KEY: process.env.AGENTMAIL_API_KEY || "",
|
||||
});
|
||||
|
||||
try {
|
||||
await client.connect();
|
||||
const result = await client.callTool("commander_agentmail", agentmailParams);
|
||||
return result;
|
||||
} finally {
|
||||
try { client.disconnect(); } catch {}
|
||||
}
|
||||
} catch (err: any) {
|
||||
return {
|
||||
content: [{ type: "text" as const, text: `Email sending failed: ${err.message}` }],
|
||||
details: { success: false, error: err.message },
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
renderCall(args, theme) {
|
||||
const p = args as SendEmailParams;
|
||||
const type = p.type || "generic";
|
||||
const to = p.to || "default";
|
||||
const label = `${type} → ${to}`;
|
||||
return new Text(theme.fg("toolTitle", theme.bold("send_email ")) + theme.fg("accent", label), 0, 0);
|
||||
},
|
||||
|
||||
renderResult(result, _options, theme) {
|
||||
const details = result.details as any;
|
||||
const text = result.content?.[0];
|
||||
const textStr = text?.type === "text" ? text.text : "";
|
||||
|
||||
if (details?.error || textStr.toLowerCase().includes("fail") || textStr.toLowerCase().includes("error")) {
|
||||
return new Text(theme.fg("error", `send_email failed: ${details?.error || textStr}`), 0, 0);
|
||||
}
|
||||
|
||||
return new Text(theme.fg("success", `send_email ✓ ${textStr || "sent"}`), 0, 0);
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user