f204ff1dd7
Add three new Google service integrations following the established Gmail/GCal pattern: - Google Docs (docs.list, docs.search, docs.read): list, search, and read document content as plain text via Docs + Drive APIs - Google Drive (drive.list, drive.search, drive.read): list, search, and read files with export support for Workspace files (Docs→text, Sheets→CSV, Slides→text) - Google Tasks (tasks.lists, tasks.list): list task lists and tasks with status, due dates, and notes Each service has its own config section, OAuth auth command, tool policy group, and test suite (53 new tests). The setup wizard now offers to configure all Google services together and run OAuth auth flows automatically after saving config. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
75 lines
2.7 KiB
TypeScript
75 lines
2.7 KiB
TypeScript
import type { Command } from 'commander';
|
|
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
import { dirname } from 'path';
|
|
import { createInterface } from 'readline/promises';
|
|
import { parse } from 'yaml';
|
|
import { getConfigPath } from './shared.js';
|
|
import { createPrompter } from './setup/prompts.js';
|
|
import { ConfigBuilder } from './setup/config.js';
|
|
import { runFirstRunWizard, runMenu } from './setup/orchestrator.js';
|
|
import { runGoogleAuth } from './setup/automation.js';
|
|
|
|
export async function runSetup(configPath: string): Promise<void> {
|
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
const p = createPrompter(rl);
|
|
|
|
try {
|
|
if (existsSync(configPath)) {
|
|
// Existing config → menu mode
|
|
const raw = readFileSync(configPath, 'utf-8');
|
|
const parsed = parse(raw) ?? {};
|
|
const builder = ConfigBuilder.fromObject(parsed);
|
|
await runMenu(p, builder);
|
|
saveConfig(configPath, builder, p);
|
|
await runGoogleAuth(p, builder.build());
|
|
} else {
|
|
// No config → first-run wizard
|
|
const builder = await runFirstRunWizard(p);
|
|
saveConfig(configPath, builder, p);
|
|
await runGoogleAuth(p, builder.build());
|
|
|
|
const shouldStart = await p.confirm('Start Flynn now?', true);
|
|
if (shouldStart) {
|
|
rl.close();
|
|
const { startDaemon } = await import('../daemon/index.js');
|
|
const { loadConfig } = await import('../config/index.js');
|
|
const config = loadConfig(configPath);
|
|
const daemon = await startDaemon(config);
|
|
await new Promise<void>(resolve => daemon.lifecycle.onShutdown(async () => resolve()));
|
|
return;
|
|
}
|
|
|
|
const wantMore = await p.confirm('Configure more features?', false);
|
|
if (wantMore) {
|
|
const raw = readFileSync(configPath, 'utf-8');
|
|
const parsed = parse(raw) ?? {};
|
|
const menuBuilder = ConfigBuilder.fromObject(parsed);
|
|
await runMenu(p, menuBuilder);
|
|
saveConfig(configPath, menuBuilder, p);
|
|
await runGoogleAuth(p, menuBuilder.build());
|
|
}
|
|
}
|
|
} finally {
|
|
rl.close();
|
|
}
|
|
}
|
|
|
|
function saveConfig(configPath: string, builder: ConfigBuilder, p: { println(msg?: string): void }): void {
|
|
const dir = dirname(configPath);
|
|
mkdirSync(dir, { recursive: true });
|
|
writeFileSync(configPath, builder.toYaml(), 'utf-8');
|
|
p.println();
|
|
p.println(`✓ Config saved to ${configPath}`);
|
|
}
|
|
|
|
export function registerSetupCommand(program: Command): void {
|
|
program
|
|
.command('setup')
|
|
.description('Interactive setup wizard')
|
|
.option('-c, --config <path>', 'Config file path')
|
|
.action(async (opts: { config?: string }) => {
|
|
const configPath = opts.config ?? getConfigPath();
|
|
await runSetup(configPath);
|
|
});
|
|
}
|