mirror of
https://github.com/beilunyang/moemail.git
synced 2026-09-05 23:36:37 +08:00
feat(cli): add MoeMail AI agent skills
This commit is contained in:
@@ -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.
|
||||||
@@ -583,6 +583,19 @@ MSG_ID=$(echo $MSG | jq -r '.messageId')
|
|||||||
CONTENT=$(moemail read --email-id $EMAIL_ID --message-id $MSG_ID --json)
|
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).
|
For full documentation, see [packages/cli/README.md](packages/cli/README.md).
|
||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ moemail wait --email-id <email_id> --timeout 120
|
|||||||
| `read` | Read email message content | `--email-id <id>`, `--message-id <id>`, `--json` |
|
| `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` |
|
| `send` | Send email from temporary address | `--email-id <id>`, `--to <address>`, `--subject <text>`, `--body <text>`, `--json` |
|
||||||
| `delete` | Delete temporary email | `--email-id <id>` |
|
| `delete` | Delete temporary email | `--email-id <id>` |
|
||||||
|
| `skill install` | Install AI agent skill | `--platform <claude\|codex\|all>` |
|
||||||
|
|
||||||
## Agent Workflow Example
|
## 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
|
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
|
## JSON Output
|
||||||
|
|
||||||
All commands support `--json` flag for structured output, making them ideal for agent automation:
|
All commands support `--json` flag for structured output, making them ideal for agent automation:
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
"build": "bun build ./src/index.ts --outdir ./dist --target=node",
|
"build": "bun build ./src/index.ts --outdir ./dist --target=node",
|
||||||
"dev": "bun run ./src/index.ts"
|
"dev": "bun run ./src/index.ts"
|
||||||
},
|
},
|
||||||
"files": ["dist"],
|
"files": ["dist", "skill"],
|
||||||
"keywords": ["email", "temporary", "cli", "agent", "ai"],
|
"keywords": ["email", "temporary", "cli", "agent", "ai"],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -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 |
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import { registerWaitCommand } from "./commands/wait.js";
|
|||||||
import { registerReadCommand } from "./commands/read.js";
|
import { registerReadCommand } from "./commands/read.js";
|
||||||
import { registerDeleteCommand } from "./commands/delete.js";
|
import { registerDeleteCommand } from "./commands/delete.js";
|
||||||
import { registerSendCommand } from "./commands/send.js";
|
import { registerSendCommand } from "./commands/send.js";
|
||||||
|
import { registerSkillCommand } from "./commands/skill.js";
|
||||||
|
|
||||||
const program = new Command();
|
const program = new Command();
|
||||||
|
|
||||||
@@ -23,5 +24,6 @@ registerWaitCommand(program);
|
|||||||
registerReadCommand(program);
|
registerReadCommand(program);
|
||||||
registerDeleteCommand(program);
|
registerDeleteCommand(program);
|
||||||
registerSendCommand(program);
|
registerSendCommand(program);
|
||||||
|
registerSkillCommand(program);
|
||||||
|
|
||||||
program.parse();
|
program.parse();
|
||||||
|
|||||||
Reference in New Issue
Block a user