feat(cli): add MoeMail AI agent skills

This commit is contained in:
ty
2026-03-30 17:08:50 +08:00
parent adec4a50df
commit 61f47626e7
7 changed files with 346 additions and 1 deletions
+88
View File
@@ -0,0 +1,88 @@
---
name: moemail
description: Use when working in this repository and an AI agent needs a temporary/disposable email address through the MoeMail CLI.
---
# MoeMail for This Repository
## When to use
Use this skill when you are inside the `moemail` repository and need to create inboxes, wait for messages, read message content, or send test emails with the MoeMail CLI.
## Install
Install the MoeMail CLI globally when it is not already available:
```bash
npm i -g @moemail/cli
```
Then confirm the binary is available:
```bash
moemail --help
```
## Preferred CLI
Prefer the installed `moemail` CLI when it is available:
```bash
CLI="moemail"
```
If the global CLI is unavailable and you are working inside this repository, fall back to the repo-local build:
```bash
CLI="node packages/cli/dist/index.js"
```
If `packages/cli/dist/index.js` is missing or stale, rebuild it first:
```bash
(cd packages/cli && bun run build)
```
## Setup
For local development against this repository:
```bash
$CLI config set api-url http://localhost:3000
$CLI config set api-key YOUR_API_KEY
```
For the hosted service, use `https://moemail.app` instead.
You can also use environment variables: `MOEMAIL_API_URL`, `MOEMAIL_API_KEY`.
## Core workflow
```bash
RESULT=$($CLI --json create --expiry 1h)
ID=$(echo "$RESULT" | jq -r '.id')
EMAIL=$(echo "$RESULT" | jq -r '.address')
MSG=$($CLI --json wait --email-id "$ID" --timeout 120)
MSG_ID=$(echo "$MSG" | jq -r '.messageId')
$CLI --json read --email-id "$ID" --message-id "$MSG_ID"
```
## Commands
| Command | Required options | Notes |
|---------|------------------|-------|
| `config set` | `<key> <value>` | keys: `api-url`, `api-key` |
| `create` | - | `--name`, `--domain`, `--expiry` |
| `list` | - | `--email-id`, `--cursor` |
| `wait` | `--email-id` | `--timeout`, `--interval` |
| `read` | `--email-id`, `--message-id` | `--format text|html` |
| `send` | `--email-id`, `--to`, `--subject`, `--content` | - |
| `delete` | `--email-id` | - |
## Important details
- Put `--json` before the subcommand.
- Call `create` once and parse both `id` and `address` from the same JSON result.
- Check both `content` and `html` when reading HTML-heavy messages.
+13
View File
@@ -583,6 +583,19 @@ MSG_ID=$(echo $MSG | jq -r '.messageId')
CONTENT=$(moemail read --email-id $EMAIL_ID --message-id $MSG_ID --json)
```
### AI Agent Skill
Install the built-in skill so AI agents (Claude Code, Codex, etc.) automatically know how to use MoeMail:
```bash
# Auto-detect installed agent platforms and install
moemail skill install
# Or specify a platform
moemail skill install --platform claude
moemail skill install --platform codex
```
For full documentation, see [packages/cli/README.md](packages/cli/README.md).
## Environment Variables
+19
View File
@@ -36,6 +36,7 @@ moemail wait --email-id <email_id> --timeout 120
| `read` | Read email message content | `--email-id <id>`, `--message-id <id>`, `--json` |
| `send` | Send email from temporary address | `--email-id <id>`, `--to <address>`, `--subject <text>`, `--body <text>`, `--json` |
| `delete` | Delete temporary email | `--email-id <id>` |
| `skill install` | Install AI agent skill | `--platform <claude\|codex\|all>` |
## Agent Workflow Example
@@ -62,6 +63,24 @@ CONTENT=$(moemail read --email-id $EMAIL_ID --message-id $MSG_ID --json)
moemail delete --email-id $EMAIL_ID
```
## AI Agent Skill
The CLI ships with a built-in skill file that teaches AI agents how to use MoeMail. Install it to your agent platform:
```bash
# Auto-detect installed platforms (Claude Code, Codex)
moemail skill install
# Install to a specific platform
moemail skill install --platform claude
moemail skill install --platform codex
# Install to all supported platforms (skip detection)
moemail skill install --platform all
```
After installation, AI agents will automatically know how to create temporary emails, wait for messages, and read content using the MoeMail CLI.
## JSON Output
All commands support `--json` flag for structured output, making them ideal for agent automation:
+1 -1
View File
@@ -10,7 +10,7 @@
"build": "bun build ./src/index.ts --outdir ./dist --target=node",
"dev": "bun run ./src/index.ts"
},
"files": ["dist"],
"files": ["dist", "skill"],
"keywords": ["email", "temporary", "cli", "agent", "ai"],
"license": "MIT",
"dependencies": {
+89
View File
@@ -0,0 +1,89 @@
---
name: moemail
description: Use when an AI agent needs a temporary/disposable email address — for receiving verification emails, testing email integrations, or any task requiring a temporary inbox via the moemail CLI
---
# MoeMail — Temporary Email for AI Agents
## Overview
MoeMail provides disposable email addresses with an agent-first CLI (`moemail`). Create inboxes, wait for messages, read content, and send emails programmatically.
## Install
Install the MoeMail CLI globally when it is not already available:
```bash
npm i -g @moemail/cli
```
Then confirm the binary is available:
```bash
moemail --help
```
## Setup
Configure once per environment:
```bash
moemail config set api-url https://moemail.app
moemail config set api-key YOUR_API_KEY
```
Or via environment variables: `MOEMAIL_API_URL`, `MOEMAIL_API_KEY`.
## Core Workflow: Receive an Email
```bash
# 1. Create inbox — capture ONCE, parse both fields
RESULT=$(moemail --json create --expiry 1h)
ID=$(echo "$RESULT" | jq -r '.id')
EMAIL=$(echo "$RESULT" | jq -r '.address')
# 2. Use $EMAIL wherever needed (registration, forms, etc.)
# 3. Wait for message (exits when message arrives or times out)
MSG=$(moemail --json wait --email-id "$ID" --timeout 120)
MSG_ID=$(echo "$MSG" | jq -r '.messageId')
# 4. Read full message content
moemail --json read --email-id "$ID" --message-id "$MSG_ID"
```
## Command Reference
| Command | Required Options | Notable Options |
|---------|-----------------|-----------------|
| `config set` | `<key> <value>` | keys: `api-url`, `api-key` |
| `create` | — | `--name`, `--domain`, `--expiry` (1h\|24h\|3d\|permanent) |
| `list` | — | `--email-id` (lists messages in mailbox), `--cursor` |
| `wait` | `--email-id` | `--timeout` (default 120s), `--interval` (default 5s) |
| `read` | `--email-id`, `--message-id` | `--format` (text\|html) |
| `send` | `--email-id`, `--to`, `--subject`, `--content` | — |
| `delete` | `--email-id` | — |
**Always put `--json` before the subcommand:**
```bash
moemail --json create --expiry 24h # ✅ correct
moemail create --expiry 24h --json # ❌ wrong position
```
## JSON Output Shapes
**create:** `{ "id": "...", "address": "user@domain.com", "expiresAt": "2025-..." }`
**wait:** `{ "messageId": "...", "from": "...", "subject": "...", "receivedAt": "2025-..." }`
**read:** `{ "id": "...", "from": "...", "to": "...", "subject": "...", "content": "plain text", "html": "...", "receivedAt": "..." }`
**send:** `{ "success": true, "remainingEmails": 10 }`
## Common Mistakes
| Mistake | Fix |
|---------|-----|
| Calling `create` twice to get id + address | Call once, save to variable, parse both fields |
| `--json` after subcommand | Move `--json` before the subcommand |
| Timeout too short for slow services | Use `--timeout 300` for unreliable senders |
| Inbox expired mid-test | Use `--expiry permanent` for long-running workflows |
| Using `content` field for HTML emails | Check both `content` (plain text) and `html` fields |
+134
View File
@@ -0,0 +1,134 @@
import { Command } from "commander";
import { log, printText } from "../output.js";
import { readFileSync, mkdirSync, writeFileSync, existsSync } from "fs";
import { join, dirname, resolve } from "path";
import { homedir } from "os";
import { fileURLToPath } from "url";
interface Platform {
id: string;
name: string;
dir: string;
skillPath: string;
}
function getRuntimeEnv(name: string): string | undefined {
const proc = Reflect.get(globalThis, "process") as
| { env?: Record<string, string | undefined> }
| undefined;
return proc?.env?.[name];
}
function getPlatforms(): Platform[] {
const home = homedir();
const codexHome = getRuntimeEnv("CODEX_HOME") || join(home, ".codex");
return [
{
id: "claude",
name: "Claude Code",
dir: join(home, ".claude"),
skillPath: join(home, ".claude", "skills", "moemail", "SKILL.md"),
},
{
id: "codex",
name: "Codex",
dir: codexHome,
skillPath: join(codexHome, "skills", "moemail", "SKILL.md"),
},
];
}
function getSkillSource(): string {
// Resolve skill file relative to the executing script
// Bundled: dist/index.js → ../skill/SKILL.md
// Source: src/commands/skill.ts → ../../skill/SKILL.md
const scriptDir = dirname(fileURLToPath(import.meta.url));
const candidates = [
join(scriptDir, "..", "skill", "SKILL.md"), // from dist/
join(scriptDir, "..", "..", "skill", "SKILL.md"), // from src/commands/
];
for (const p of candidates) {
const resolved = resolve(p);
if (existsSync(resolved)) {
return readFileSync(resolved, "utf-8");
}
}
throw new Error("SKILL.md not found");
}
function installTo(platform: Platform, content: string): boolean {
try {
mkdirSync(dirname(platform.skillPath), { recursive: true });
writeFileSync(platform.skillPath, content, "utf-8");
return true;
} catch (e: any) {
log(`Error installing to ${platform.name}: ${e.message}`);
return false;
}
}
export function registerSkillCommand(program: Command) {
const skill = program
.command("skill")
.description("Manage MoeMail AI agent skill");
skill
.command("install")
.description("Install MoeMail skill to AI agent platforms")
.option(
"--platform <platform>",
"target platform: claude, codex, all (default: auto-detect)"
)
.action((opts) => {
let content: string;
try {
content = getSkillSource();
} catch {
log("Error: Could not read skill file from package.");
process.exit(1);
}
const platforms = getPlatforms();
const platformFlag = opts.platform?.toLowerCase();
let targets: Platform[];
if (platformFlag === "all") {
targets = platforms;
} else if (platformFlag) {
const match = platforms.find((p) => p.id === platformFlag);
if (!match) {
log(
`Error: Unknown platform "${opts.platform}". Supported: claude, codex, all`
);
process.exit(1);
}
targets = [match];
} else {
// Auto-detect: only platforms whose base dir exists
targets = platforms.filter((p) => existsSync(p.dir));
if (targets.length === 0) {
log(
"No supported agent platform detected. Use --platform to specify manually."
);
log("Supported: claude, codex, all");
process.exit(1);
}
}
let installed = 0;
let failed = 0;
for (const t of targets) {
if (installTo(t, content)) {
printText(`Installed skill to ${t.name} (${t.skillPath})`);
installed++;
} else {
failed++;
}
}
if (installed === 0 || failed > 0) {
process.exit(1);
}
});
}
+2
View File
@@ -7,6 +7,7 @@ import { registerWaitCommand } from "./commands/wait.js";
import { registerReadCommand } from "./commands/read.js";
import { registerDeleteCommand } from "./commands/delete.js";
import { registerSendCommand } from "./commands/send.js";
import { registerSkillCommand } from "./commands/skill.js";
const program = new Command();
@@ -23,5 +24,6 @@ registerWaitCommand(program);
registerReadCommand(program);
registerDeleteCommand(program);
registerSendCommand(program);
registerSkillCommand(program);
program.parse();