import { stringify } from 'yaml'; interface ProviderConfig { provider: string; model: string; api_key?: string; auth_token?: string; endpoint?: string; } interface EmbeddingConfig { provider: string; api_key?: string; endpoint?: string; } export interface SetupConfig { log_level?: string; models: Record>; server: { port?: number; localhost?: boolean; token?: string; lock?: boolean; tailscale?: { serve?: boolean }; queue?: { mode?: 'collect' | 'followup' | 'steer' | 'steer_backlog' | 'interrupt'; }; } & Record; hooks?: Record; telegram?: { bot_token: string; allowed_chat_ids: number[] }; discord?: { bot_token: string; allowed_guild_ids: string[] }; slack?: { bot_token: string; app_token: string; signing_secret: string; allowed_channel_ids: string[] }; whatsapp?: { allowed_numbers: string[] }; memory?: { embedding?: { enabled?: boolean; provider?: string; api_key?: string; endpoint?: string } }; audio?: { enabled?: boolean; talk_mode?: { enabled?: boolean; wake_phrase?: string; timeout_ms?: number; allow_manual_toggle?: boolean; }; }; tts?: { enabled?: boolean; fallback?: { max_attempts?: number; failure_cooldown_ms?: number; }; }; sandbox?: { enabled?: boolean }; pairing?: { enabled?: boolean }; tools?: { profile?: string }; agents?: { sensitive_mode?: 'deny_without_elevation' | 'confirm_without_elevation'; }; agent_configs?: Record; intents?: { enabled?: boolean; match_threshold?: number; rules?: Array>; }; automation?: { cron?: Array>; webhooks?: Array>; gmail?: { enabled?: boolean }; gcal?: { enabled?: boolean }; gdocs?: { enabled?: boolean }; gdrive?: { enabled?: boolean }; gtasks?: { enabled?: boolean }; heartbeat?: { enabled?: boolean }; daily_briefing?: { enabled?: boolean; schedule?: string; output?: { channel?: string; peer?: string; }; model_tier?: 'fast' | 'default' | 'complex' | 'local'; }; minio_sync?: { enabled?: boolean }; } & Record; backup?: { enabled?: boolean; schedule?: string; run_on_start?: boolean; notify?: { channel: string; peer: string }; } & Record; [key: string]: unknown; } interface OperatorPackOptions { outputChannel: string; outputPeer: string; backupSchedule: string; dailyBriefingSchedule: string; enableMinioSync?: boolean; } interface ResearchAgentOptions { modelTier: 'fast' | 'default' | 'complex' | 'local'; } interface PersonalAssistantModeOptions { enableTalkMode?: boolean; enableTts?: boolean; } export class ConfigBuilder { private config: SetupConfig; constructor() { this.config = { log_level: 'info', models: {}, server: { port: 18800, localhost: false }, hooks: { confirm: [ 'shell.*', 'process.start', 'process.kill', 'browser.*', 'message.send', 'cron.create', 'cron.delete', 'file.write', 'file.patch', ], log: ['web.*', 'file.read'], silent: ['notify'], }, tools: { profile: 'full' }, agents: { sensitive_mode: 'confirm_without_elevation' }, }; } static fromObject(obj: Record): ConfigBuilder { const builder = new ConfigBuilder(); builder.config = structuredClone(obj) as SetupConfig; return builder; } setProvider(tier: 'default' | 'fast' | 'complex' | 'local', cfg: ProviderConfig): void { const models = (this.config.models ?? {}) as SetupConfig['models']; const entry: ProviderConfig & Record = { provider: cfg.provider, model: cfg.model }; if (cfg.api_key) {entry.api_key = cfg.api_key;} if (cfg.auth_token) {entry.auth_token = cfg.auth_token;} if (cfg.endpoint) {entry.endpoint = cfg.endpoint;} models[tier] = entry; this.config.models = models; } setTelegram(botToken: string, chatIds: number[]): void { this.config.telegram = { bot_token: botToken, allowed_chat_ids: chatIds }; } setDiscord(botToken: string, guildIds: string[]): void { this.config.discord = { bot_token: botToken, allowed_guild_ids: guildIds }; } setSlack(botToken: string, appToken: string, signingSecret: string, channelIds: string[]): void { this.config.slack = { bot_token: botToken, app_token: appToken, signing_secret: signingSecret, allowed_channel_ids: channelIds }; } setWhatsApp(allowedNumbers: string[]): void { this.config.whatsapp = { allowed_numbers: allowedNumbers }; } setGatewayPort(port: number): void { const server = (this.config.server ?? {}) as Record; server.port = port; this.config.server = server; } setGatewayToken(token: string): void { const server = (this.config.server ?? {}) as Record; server.token = token; this.config.server = server; } setGatewayLock(enabled: boolean): void { const server = (this.config.server ?? {}) as Record; server.lock = enabled; this.config.server = server; } setTailscaleServe(enabled: boolean): void { const server = (this.config.server ?? {}) as Record; const tailscale = (server.tailscale ?? {}) as Record; tailscale.serve = enabled; server.tailscale = tailscale; this.config.server = server; } setMemoryEmbedding(cfg: EmbeddingConfig): void { const memory = (this.config.memory ?? {}) as Record; const embedding: Record = { enabled: true, provider: cfg.provider }; if (cfg.api_key) {embedding.api_key = cfg.api_key;} if (cfg.endpoint) {embedding.endpoint = cfg.endpoint;} memory.embedding = embedding; this.config.memory = memory; } setSandboxEnabled(enabled: boolean): void { this.config.sandbox = { enabled }; } setPairingEnabled(enabled: boolean): void { this.config.pairing = { enabled }; } setToolProfile(profile: string): void { this.config.tools = { profile }; } setSensitiveMode(mode: 'deny_without_elevation' | 'confirm_without_elevation'): void { const agents = (this.config.agents ?? {}) as Record; agents.sensitive_mode = mode; this.config.agents = agents as SetupConfig['agents']; } setResearchAgentEnabled(options: ResearchAgentOptions): void { const agentConfigs = (this.config.agent_configs ?? {}) as Record>; const existing = (agentConfigs.research ?? {}) as Record; agentConfigs.research = { ...existing, model_tier: options.modelTier, tool_profile: 'messaging', system_prompt: [ 'You are a research agent. Find, verify, and synthesize information for the operator.', 'Prefer primary sources and include concrete dates and links when available.', 'Keep output structured and concise.', ].join(' '), }; this.config.agent_configs = agentConfigs; } setWebhooksEnabled(secret?: string): void { const automation = (this.config.automation ?? {}) as Record; if (secret) { automation.webhooks = [{ name: 'default', secret, message: '{{body}}', output: { channel: 'webchat', peer: 'webhook' }, enabled: true }]; } else { automation.webhooks = automation.webhooks ?? []; } this.config.automation = automation; } setGmailEnabled(credentialsFile: string, outputChannel: string, outputPeer: string): void { const automation = (this.config.automation ?? {}) as Record; automation.gmail = { enabled: true, credentials_file: credentialsFile, output: { channel: outputChannel, peer: outputPeer } }; this.config.automation = automation; } setGcalEnabled(credentialsFile: string): void { const automation = (this.config.automation ?? {}) as Record; automation.gcal = { enabled: true, credentials_file: credentialsFile }; this.config.automation = automation; } setGdocsEnabled(credentialsFile: string): void { const automation = (this.config.automation ?? {}) as Record; automation.gdocs = { enabled: true, credentials_file: credentialsFile }; this.config.automation = automation; } setGdriveEnabled(credentialsFile: string): void { const automation = (this.config.automation ?? {}) as Record; automation.gdrive = { enabled: true, credentials_file: credentialsFile }; this.config.automation = automation; } setGtasksEnabled(credentialsFile: string): void { const automation = (this.config.automation ?? {}) as Record; automation.gtasks = { enabled: true, credentials_file: credentialsFile }; this.config.automation = automation; } setCronEnabled(): void { const automation = (this.config.automation ?? {}) as Record; if (!automation.cron) {automation.cron = [];} this.config.automation = automation; } applyOperatorPack(options: OperatorPackOptions): void { const automation = (this.config.automation ?? {}) as Record; const backup = (this.config.backup ?? {}) as Record; const memory = (this.config.memory ?? {}) as Record; backup.enabled = true; backup.schedule = options.backupSchedule; backup.run_on_start = true; backup.notify = { channel: options.outputChannel, peer: options.outputPeer }; // Personal-assistant mode defaults: proactive push and continuity-first memory cadence. automation.delivery_mode = 'announce'; automation.heartbeat = { enabled: true, notify: { channel: options.outputChannel, peer: options.outputPeer }, interval: '5m', failure_threshold: 2, notify_cooldown: '30m', }; automation.daily_briefing = { enabled: true, schedule: options.dailyBriefingSchedule, output: { channel: options.outputChannel, peer: options.outputPeer }, dedupe_per_local_day: true, model_tier: 'fast', }; if (options.enableMinioSync ?? true) { automation.minio_sync = { enabled: true, interval: '6h', run_on_start: true, notify: { channel: options.outputChannel, peer: options.outputPeer }, tasks: [ { prefix: 'knowledge/', namespace_base: 'global/knowledge/minio', mode: 'append', max_objects: 20, max_chars_per_object: 8000, force: false, }, ], }; } memory.daily_log = { enabled: true, namespace_prefix: 'daily', }; memory.proactive_extract = { enabled: true, min_tool_calls: 1, namespace: 'global', }; this.config.automation = automation; this.config.backup = backup; this.config.memory = memory; } applyPersonalAssistantMode(options?: PersonalAssistantModeOptions): void { const automation = (this.config.automation ?? {}) as Record; const memory = (this.config.memory ?? {}) as Record; const audio = (this.config.audio ?? {}) as Record; const talkMode = (audio.talk_mode ?? {}) as Record; const tts = (this.config.tts ?? {}) as Record; const ttsFallback = (tts.fallback ?? {}) as Record; const server = (this.config.server ?? {}) as Record; const queue = (server.queue ?? {}) as Record; automation.delivery_mode = 'announce'; memory.daily_log = { enabled: true, namespace_prefix: 'daily', }; memory.proactive_extract = { enabled: true, min_tool_calls: 2, namespace: 'global', }; talkMode.enabled = options?.enableTalkMode ?? true; talkMode.wake_phrase = 'hey flynn'; talkMode.timeout_ms = 120000; talkMode.allow_manual_toggle = true; audio.talk_mode = talkMode; audio.enabled = Boolean(audio.enabled || talkMode.enabled); tts.enabled = options?.enableTts ?? true; ttsFallback.max_attempts = 3; ttsFallback.failure_cooldown_ms = 60000; tts.fallback = ttsFallback; queue.mode = 'interrupt'; server.queue = queue; this.config.automation = automation; this.config.memory = memory; this.config.audio = audio; this.config.tts = tts; this.config.server = server as SetupConfig['server']; } build(): SetupConfig { return structuredClone(this.config) as SetupConfig; } toYaml(): string { return stringify(this.config, { lineWidth: 120 }); } }