Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions packages/email-mcp/src/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
getAgentEmailHome,
ensureGmailProfileMatchesIntent,
getEffectiveSendAllowlistPath,
inferProviderFromMailbox,
loadConfig,
saveConfig,
isDirectCliRun,
Expand Down Expand Up @@ -422,6 +423,47 @@ describe('cli/Configure Subcommand', () => {
});
});

describe('cli/Provider Inference', () => {
it('infers gmail from gmail.com', () => {
expect(inferProviderFromMailbox('steven@gmail.com')).toBe('gmail');
});

it('infers gmail case-insensitively', () => {
expect(inferProviderFromMailbox('Steven@GMAIL.COM')).toBe('gmail');
expect(inferProviderFromMailbox('Steven@Gmail.com')).toBe('gmail');
});

it('infers gmail from googlemail.com (legacy variant)', () => {
expect(inferProviderFromMailbox('user@googlemail.com')).toBe('gmail');
});

it('infers gmail despite surrounding whitespace', () => {
expect(inferProviderFromMailbox(' steven@gmail.com ')).toBe('gmail');
});

it('infers microsoft from each consumer domain', () => {
expect(inferProviderFromMailbox('user@outlook.com')).toBe('microsoft');
expect(inferProviderFromMailbox('user@hotmail.com')).toBe('microsoft');
expect(inferProviderFromMailbox('user@live.com')).toBe('microsoft');
expect(inferProviderFromMailbox('user@msn.com')).toBe('microsoft');
});

it('returns undefined for custom business domains', () => {
expect(inferProviderFromMailbox('steven@usejunior.com')).toBeUndefined();
});

it('returns undefined for inputs that are not an email address', () => {
expect(inferProviderFromMailbox('')).toBeUndefined();
expect(inferProviderFromMailbox('not-an-email')).toBeUndefined();
expect(inferProviderFromMailbox(undefined)).toBeUndefined();
});

it('does not pattern-match regional variants (hotmail.co.uk, outlook.fr) — exact match only', () => {
expect(inferProviderFromMailbox('user@hotmail.co.uk')).toBeUndefined();
expect(inferProviderFromMailbox('user@outlook.fr')).toBeUndefined();
});
});

describe('cli/Gmail Account Intent Checks', () => {
let tmpHome: string;
let originalHome: string | undefined;
Expand Down Expand Up @@ -663,6 +705,27 @@ describe('cli/Gmail Configure', () => {
clientSecret: 'env-client-secret',
});
});

it('Scenario: Configure without --provider infers gmail from a gmail.com mailbox', async () => {
process.env['AGENT_EMAIL_GMAIL_CLIENT_ID'] = 'env-client-id';
process.env['AGENT_EMAIL_GMAIL_CLIENT_SECRET'] = 'env-client-secret';

const exitCode = await runCli([
'configure',
'--mailbox',
'steven.obiajulu@gmail.com',
]);

expect(exitCode).toBe(0);
expect(gmailMockState.savedMetadata).toMatchObject({
provider: 'gmail',
mailboxName: 'steven.obiajulu@gmail.com',
emailAddress: 'steven.obiajulu@gmail.com',
});
expect(console.error).toHaveBeenCalledWith(
expect.stringContaining('Inferred provider "gmail" from mailbox domain "gmail.com"'),
);
});
});

describe('cli/NemoClaw Setup', () => {
Expand Down
32 changes: 31 additions & 1 deletion packages/email-mcp/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,28 @@ async function runGmailConfigure(opts: CliOptions): Promise<number> {
return 0;
}

export function inferProviderFromMailbox(
mailbox: string | undefined,
): 'gmail' | 'microsoft' | undefined {
if (!mailbox) return undefined;
const normalized = normalizeMailboxValue(mailbox);
if (!normalized.includes('@')) return undefined;
const domain = normalized.split('@').pop();
if (!domain) return undefined;

const GMAIL_DOMAINS = new Set(['gmail.com', 'googlemail.com']);
const MICROSOFT_CONSUMER_DOMAINS = new Set([
'outlook.com',
'hotmail.com',
'live.com',
'msn.com',
]);

if (GMAIL_DOMAINS.has(domain)) return 'gmail';
if (MICROSOFT_CONSUMER_DOMAINS.has(domain)) return 'microsoft';
return undefined;
}

export async function runConfigure(opts: CliOptions): Promise<number> {
if (opts.nemoclaw) {
console.error('[email-agent-mcp] NemoClaw bootstrap — adding egress domains:');
Expand All @@ -886,7 +908,15 @@ export async function runConfigure(opts: CliOptions): Promise<number> {
}

const mailboxName = opts.mailbox ?? 'default';
const provider = opts.provider ?? 'microsoft';
const inferredProvider = opts.provider ? undefined : inferProviderFromMailbox(opts.mailbox);
const provider = opts.provider ?? inferredProvider ?? 'microsoft';

if (inferredProvider && !opts.provider) {
const domain = normalizeMailboxValue(opts.mailbox ?? '').split('@').pop();
console.error(
`[email-agent-mcp] Inferred provider "${inferredProvider}" from mailbox domain "${domain}". Pass --provider to override.`,
);
}

if (provider === 'gmail') {
try {
Expand Down
Loading