feat(setup): add contextual help text to all wizard flows
Each setup section now explains what's needed before prompting: - Providers: links to API key consoles (Anthropic, OpenAI, Gemini, etc.) - Channels: step-by-step bot creation (Telegram @BotFather, Discord dev portal, Slack app setup, WhatsApp QR) - Gmail: Google Cloud Console OAuth setup walkthrough - Memory: explains what vector search does and key reuse - Security: describes each option (sandbox, pairing, tool profiles) - Gateway: explains auth token, Tailscale Serve, lock mode Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,18 +5,27 @@ export async function setupAutomation(p: Prompter, builder: ConfigBuilder): Prom
|
||||
const cron = await p.confirm('Enable cron scheduler?', false);
|
||||
if (cron) {
|
||||
builder.setCronEnabled();
|
||||
p.println('✓ Cron enabled — add jobs to config.yaml later');
|
||||
p.println('✓ Cron enabled — add jobs to config.yaml under automation.cron.jobs[]');
|
||||
}
|
||||
|
||||
const webhooks = await p.confirm('Enable webhook receiver?', false);
|
||||
if (webhooks) {
|
||||
p.println(' Webhooks accept HTTP POST at /webhooks/:name on the gateway port.');
|
||||
p.println(' Set a shared secret to verify requests via HMAC signature.');
|
||||
const secret = await p.ask('Webhook shared secret (optional)', '');
|
||||
builder.setWebhooksEnabled(secret || undefined);
|
||||
p.println('✓ Webhooks enabled');
|
||||
p.println('✓ Webhooks enabled — define triggers in config.yaml under automation.webhooks[]');
|
||||
}
|
||||
|
||||
const gmail = await p.confirm('Enable Gmail watcher?', false);
|
||||
if (gmail) {
|
||||
p.println(' To set up Gmail access:');
|
||||
p.println(' 1. Go to https://console.cloud.google.com → create or select a project');
|
||||
p.println(' 2. Enable the Gmail API and Cloud Pub/Sub API');
|
||||
p.println(' 3. Go to APIs & Services → Credentials → Create Credentials → OAuth client ID');
|
||||
p.println(' 4. Choose "Desktop app", download the JSON file');
|
||||
p.println(' 5. Save it as ~/.config/flynn/gmail-credentials.json');
|
||||
p.println(' On first run, Flynn will open a browser for OAuth consent and save the token.');
|
||||
const creds = await p.ask('OAuth credentials file', '~/.config/flynn/gmail-credentials.json');
|
||||
builder.setGmailEnabled(creds, 'webchat', 'gmail');
|
||||
p.println('✓ Gmail watcher enabled');
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { Prompter } from './prompts.js';
|
||||
import type { ConfigBuilder } from './config.js';
|
||||
|
||||
async function setupTelegram(p: Prompter, builder: ConfigBuilder): Promise<void> {
|
||||
p.println(' 1. Message @BotFather on Telegram and use /newbot to create a bot');
|
||||
p.println(' 2. Copy the bot token it gives you');
|
||||
p.println(' 3. To find your chat ID, message @userinfobot or @RawDataBot');
|
||||
const botToken = await p.password('Bot token (from @BotFather)');
|
||||
const chatIdsRaw = await p.ask('Allowed chat IDs (comma-separated)');
|
||||
const chatIds = chatIdsRaw.split(',').map(s => parseInt(s.trim(), 10)).filter(n => !isNaN(n));
|
||||
@@ -14,6 +17,11 @@ async function setupTelegram(p: Prompter, builder: ConfigBuilder): Promise<void>
|
||||
}
|
||||
|
||||
async function setupDiscord(p: Prompter, builder: ConfigBuilder): Promise<void> {
|
||||
p.println(' 1. Go to https://discord.com/developers/applications');
|
||||
p.println(' 2. Create an application → Bot → copy the bot token');
|
||||
p.println(' 3. Enable MESSAGE CONTENT intent under Bot settings');
|
||||
p.println(' 4. Invite bot to your server with OAuth2 URL Generator (bot scope + Send Messages)');
|
||||
p.println(' 5. Guild ID: right-click your server → Copy Server ID (enable Developer Mode in settings)');
|
||||
const botToken = await p.password('Bot token');
|
||||
const guildIdsRaw = await p.ask('Allowed guild IDs (comma-separated, or * for all)');
|
||||
const guildIds = guildIdsRaw === '*' ? [] : guildIdsRaw.split(',').map(s => s.trim()).filter(Boolean);
|
||||
@@ -22,6 +30,11 @@ async function setupDiscord(p: Prompter, builder: ConfigBuilder): Promise<void>
|
||||
}
|
||||
|
||||
async function setupSlack(p: Prompter, builder: ConfigBuilder): Promise<void> {
|
||||
p.println(' 1. Go to https://api.slack.com/apps and create a new app');
|
||||
p.println(' 2. Enable Socket Mode → generate an App Token (xapp-...)');
|
||||
p.println(' 3. Under OAuth & Permissions, install to workspace → copy Bot Token (xoxb-...)');
|
||||
p.println(' 4. Under Basic Information → copy Signing Secret');
|
||||
p.println(' 5. Channel IDs: right-click a channel → View channel details → copy the ID at bottom');
|
||||
const botToken = await p.password('Bot token (xoxb-...)');
|
||||
const appToken = await p.password('App token (xapp-...)');
|
||||
const signingSecret = await p.password('Signing secret');
|
||||
|
||||
@@ -2,9 +2,12 @@ import type { Prompter } from './prompts.js';
|
||||
import type { ConfigBuilder } from './config.js';
|
||||
|
||||
export async function setupGateway(p: Prompter, builder: ConfigBuilder): Promise<void> {
|
||||
p.println(' The gateway serves the web dashboard and WebSocket API.');
|
||||
const port = await p.ask('Gateway port', '18800');
|
||||
builder.setGatewayPort(parseInt(port, 10) || 18800);
|
||||
|
||||
p.println();
|
||||
p.println(' An auth token protects gateway access (required for remote/Tailscale use).');
|
||||
const wantAuth = await p.confirm('Set auth token?', false);
|
||||
if (wantAuth) {
|
||||
const token = await p.password('Auth token');
|
||||
@@ -12,12 +15,16 @@ export async function setupGateway(p: Prompter, builder: ConfigBuilder): Promise
|
||||
p.println('✓ Gateway auth token set');
|
||||
}
|
||||
|
||||
p.println();
|
||||
p.println(' Tailscale Serve exposes the gateway on your tailnet (requires tailscale CLI).');
|
||||
const tailscale = await p.confirm('Enable Tailscale Serve?', false);
|
||||
if (tailscale) {
|
||||
builder.setTailscaleServe(true);
|
||||
p.println('✓ Tailscale Serve enabled');
|
||||
}
|
||||
|
||||
p.println();
|
||||
p.println(' Gateway lock limits connections to one client at a time.');
|
||||
const lock = await p.confirm('Enable gateway lock (single client)?', false);
|
||||
if (lock) {
|
||||
builder.setGatewayLock(true);
|
||||
|
||||
@@ -11,9 +11,13 @@ const EMBEDDING_PROVIDERS = [
|
||||
const REUSABLE_PROVIDERS = ['openai', 'gemini'];
|
||||
|
||||
export async function setupMemory(p: Prompter, builder: ConfigBuilder): Promise<void> {
|
||||
p.println(' Vector search enables semantic memory — Flynn remembers and retrieves');
|
||||
p.println(' information based on meaning, not just keywords.');
|
||||
const enable = await p.confirm('Enable vector search for semantic memory?', false);
|
||||
if (!enable) return;
|
||||
|
||||
p.println(' Pick a provider to generate embeddings (vector representations of text).');
|
||||
p.println(' If you already configured OpenAI or Gemini as a model, you can reuse that key.');
|
||||
const provider = await p.choose('Embedding provider:', EMBEDDING_PROVIDERS);
|
||||
|
||||
const config = builder.build();
|
||||
|
||||
@@ -26,9 +26,23 @@ const SECOND_TIER: ProviderDef[] = [
|
||||
{ name: 'GitHub Models', provider: 'github', defaultModel: 'claude-sonnet-4-20250514', needsApiKey: false, needsEndpoint: false },
|
||||
];
|
||||
|
||||
const PROVIDER_HELP: Record<string, string> = {
|
||||
anthropic: 'Get your API key at https://console.anthropic.com/settings/keys',
|
||||
openai: 'Get your API key at https://platform.openai.com/api-keys',
|
||||
ollama: 'Ollama runs locally — install from https://ollama.com and run: ollama serve',
|
||||
gemini: 'Get your API key at https://aistudio.google.com/apikey',
|
||||
openrouter: 'Get your API key at https://openrouter.ai/keys (supports 200+ models)',
|
||||
xai: 'Get your API key at https://console.x.ai',
|
||||
bedrock: 'Uses AWS credentials from environment (~/.aws/credentials or IAM role)',
|
||||
github: 'Uses GitHub Copilot — authenticate via OAuth device flow on first use',
|
||||
};
|
||||
|
||||
async function configureProvider(p: Prompter, def: ProviderDef): Promise<{
|
||||
provider: string; model: string; api_key?: string; endpoint?: string;
|
||||
}> {
|
||||
const help = PROVIDER_HELP[def.provider];
|
||||
if (help) p.println(` ${help}`);
|
||||
|
||||
const config: Record<string, string> = { provider: def.provider };
|
||||
if (def.needsApiKey) config.api_key = await p.password(def.apiKeyLabel ?? 'API key');
|
||||
if (def.needsEndpoint) config.endpoint = await p.ask('Host', def.defaultEndpoint);
|
||||
|
||||
@@ -9,18 +9,29 @@ const TOOL_PROFILES = [
|
||||
];
|
||||
|
||||
export async function setupSecurity(p: Prompter, builder: ConfigBuilder): Promise<void> {
|
||||
p.println(' Docker sandboxing runs tool commands in isolated containers.');
|
||||
p.println(' Requires Docker installed and running.');
|
||||
const sandbox = await p.confirm('Enable Docker sandboxing?', false);
|
||||
if (sandbox) {
|
||||
builder.setSandboxEnabled(true);
|
||||
p.println('✓ Docker sandboxing enabled');
|
||||
}
|
||||
|
||||
p.println();
|
||||
p.println(' DM pairing requires unknown senders to enter a code before chatting.');
|
||||
p.println(' Generate codes via the gateway or TUI /pair command.');
|
||||
const pairing = await p.confirm('Enable DM pairing for unknown senders?', false);
|
||||
if (pairing) {
|
||||
builder.setPairingEnabled(true);
|
||||
p.println('✓ DM pairing enabled');
|
||||
}
|
||||
|
||||
p.println();
|
||||
p.println(' Tool profiles control which tools the agent can use:');
|
||||
p.println(' full — all tools available (file, shell, web, memory, messaging)');
|
||||
p.println(' coding — file system + shell + sessions + memory (no messaging/web)');
|
||||
p.println(' messaging — send messages only (no file/shell access)');
|
||||
p.println(' minimal — status checks only (read-only, safest)');
|
||||
const profile = await p.choose('Tool policy profile:', TOOL_PROFILES);
|
||||
builder.setToolProfile(profile);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user