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
+31
View File
@@ -2,6 +2,7 @@ import type { Command } from 'commander';
import type { Config } from '../config/index.js';
import { getConfigPath, getDataDir, formatStatus, resolveOverlayPath } from './shared.js';
import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'fs';
import { homedir } from 'os';
import { resolve, join } from 'path';
import { parse } from 'yaml';
import { configSchema } from '../config/schema.js';
@@ -216,6 +217,35 @@ const checkTailscale: Check = async (ctx) => {
}
};
function expandPath(p: string): string {
if (p.startsWith('~/') || p === '~') {
return resolve(homedir(), p.slice(2));
}
return resolve(p);
}
const checkGmail: Check = async (ctx) => {
if (!ctx.config) {
return { status: 'skip', label: 'Gmail configured', detail: '(config invalid)' };
}
const gmail = ctx.config.automation.gmail;
if (!gmail?.enabled) {
return { status: 'skip', label: 'Gmail configured', detail: '(not enabled)' };
}
const credentialsPath = expandPath(gmail.credentials_file ?? '~/.config/flynn/gmail-credentials.json');
if (!existsSync(credentialsPath)) {
return { status: 'fail', label: 'Gmail configured', detail: `credentials file not found: ${credentialsPath}` };
}
const tokenPath = expandPath(gmail.token_file ?? '~/.config/flynn/gmail-token.json');
if (!existsSync(tokenPath)) {
return { status: 'warn', label: 'Gmail configured', detail: 'run `flynn gmail-auth` to authenticate' };
}
return { status: 'pass', label: 'Gmail configured', detail: `(output: ${gmail.output.channel}/${gmail.output.peer})` };
};
const allChecks: Check[] = [
checkConfigExists,
checkOverlayExists,
@@ -226,6 +256,7 @@ const allChecks: Check[] = [
checkSessionDb,
checkModelConnectivity,
checkTelegram,
checkGmail,
checkMcpServers,
checkSkills,
checkTailscale,