mirror of
https://github.com/beilunyang/moemail.git
synced 2026-06-23 00:14:51 +08:00
Extract the HTTP client and config into a new @moemail/core package shared by the CLI and a new @moemail/mcp server, so both frontends talk to the same MoeMail API through one code path. - core: api client (now throws typed ConfigError/AuthError/PermissionError/ QuotaError instead of process.exit), config, msToIso, and a transport- agnostic pollForNewMessage helper. - cli: consume @moemail/core; route command errors through a shared fail() that preserves exit codes (config/auth = 2, else = 1). Bump to 1.0.0. - mcp: new stdio MCP server exposing 8 tools (create/list/read/wait/send/ delete); wait_for_email is bounded and returns a timeout status to retry. Configured via MOEMAIL_API_KEY / MOEMAIL_API_URL env. Release 1.0.0. Docs: - Fix packages/cli/README.md (config set, send --content not --body, full flag table). - Add MCP section to both root READMEs; complete the CLI command list (send, list, message-level delete). - SKILL.md: --json works before or after the subcommand. - Ignore bun.lock in package gitignores. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
61 lines
1.6 KiB
TypeScript
61 lines
1.6 KiB
TypeScript
import { api } from "./api.js";
|
|
|
|
function sleep(ms: number): Promise<void> {
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
}
|
|
|
|
export interface NewMessage {
|
|
id: string;
|
|
from_address: string;
|
|
subject: string;
|
|
received_at?: number;
|
|
}
|
|
|
|
export interface PollResult {
|
|
status: "received" | "timeout";
|
|
message?: NewMessage;
|
|
elapsedSec: number;
|
|
}
|
|
|
|
/**
|
|
* Poll a mailbox until a message that wasn't present at the start arrives, or
|
|
* the timeout elapses. Transport-agnostic: the CLI wraps it with stderr
|
|
* progress, the MCP server returns the result as structured JSON.
|
|
*
|
|
* On timeout this resolves with `status: "timeout"` rather than throwing, so an
|
|
* MCP client can simply call the tool again to keep waiting.
|
|
*/
|
|
export async function pollForNewMessage(
|
|
emailId: string,
|
|
opts: {
|
|
timeoutMs: number;
|
|
intervalMs: number;
|
|
onTick?: (elapsedSec: number) => void;
|
|
},
|
|
): Promise<PollResult> {
|
|
const initial = (await api.listMessages(emailId)) as any;
|
|
const knownIds = new Set<string>(initial.messages.map((m: any) => m.id));
|
|
|
|
const startTime = Date.now();
|
|
|
|
while (true) {
|
|
const elapsedSec = Math.floor((Date.now() - startTime) / 1000);
|
|
if (elapsedSec >= opts.timeoutMs / 1000) {
|
|
return { status: "timeout", elapsedSec };
|
|
}
|
|
|
|
opts.onTick?.(elapsedSec);
|
|
await sleep(opts.intervalMs);
|
|
|
|
const current = (await api.listMessages(emailId)) as any;
|
|
const fresh = current.messages.filter((m: any) => !knownIds.has(m.id));
|
|
if (fresh.length > 0) {
|
|
return {
|
|
status: "received",
|
|
message: fresh[0],
|
|
elapsedSec: Math.floor((Date.now() - startTime) / 1000),
|
|
};
|
|
}
|
|
}
|
|
}
|