feat(cli): add gmail-auth command for OAuth2 token setup

Implements `flynn gmail-auth` to complete the OAuth2 flow that
GmailWatcher references but was never built. Supports local callback
server (default) and --manual paste mode. Adds Gmail health check
to `flynn doctor`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
William Valentin
2026-02-10 10:33:01 -08:00
parent f4b9c850ab
commit ff03f74404
6 changed files with 434 additions and 0 deletions
+51
View File
@@ -112,6 +112,57 @@ models:
expect(sessionDb?.status).toBe('pass');
});
it('reports SKIP for Gmail when not enabled', async () => {
mkdirSync(testDir, { recursive: true });
const configPath = join(testDir, 'config.yaml');
writeFileSync(configPath, `
telegram:
bot_token: "test-token"
allowed_chat_ids: [123]
models:
default:
provider: anthropic
model: claude-sonnet
`);
const ctx: DoctorContext = { configPath, dataDir: testDir };
const results = await runChecks(ctx);
const gmailCheck = results.find(r => r.label.includes('Gmail'));
expect(gmailCheck?.status).toBe('skip');
});
it('reports WARN for Gmail when token missing', async () => {
mkdirSync(testDir, { recursive: true });
const configPath = join(testDir, 'config.yaml');
const credsPath = join(testDir, 'gmail-creds.json');
writeFileSync(credsPath, '{}');
writeFileSync(configPath, `
telegram:
bot_token: "test-token"
allowed_chat_ids: [123]
models:
default:
provider: anthropic
model: claude-sonnet
automation:
gmail:
enabled: true
credentials_file: "${credsPath}"
token_file: "${join(testDir, 'nonexistent-token.json')}"
output:
channel: telegram
peer: "123"
`);
const ctx: DoctorContext = { configPath, dataDir: testDir };
const results = await runChecks(ctx);
const gmailCheck = results.find(r => r.label.includes('Gmail'));
expect(gmailCheck?.status).toBe('warn');
expect(gmailCheck?.detail).toContain('flynn gmail-auth');
});
it('skips downstream checks when config is invalid', async () => {
const ctx: DoctorContext = { configPath: '/nonexistent/config.yaml', dataDir: testDir };
const results = await runChecks(ctx);