feat(setup): add config builder and summary renderer

Add ConfigBuilder class to accumulate wizard answers into config objects with YAML
serialization, and renderSummary function to display configuration summary. Includes
9 test cases covering provider setup, channel configuration, and feature flags.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
William Valentin
2026-02-10 09:29:56 -08:00
parent 9cc03187b0
commit d35ce2beb5
3 changed files with 273 additions and 0 deletions
+141
View File
@@ -0,0 +1,141 @@
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 class ConfigBuilder {
private config: Record<string, unknown>;
constructor() {
this.config = {
log_level: 'info',
models: {},
server: { port: 18800, localhost: true },
hooks: {
confirm: ['shell.*', 'file.write', 'file.patch'],
log: ['web.*', 'file.read'],
silent: ['notify'],
},
};
}
static fromObject(obj: Record<string, unknown>): ConfigBuilder {
const builder = new ConfigBuilder();
builder.config = structuredClone(obj);
return builder;
}
setProvider(tier: 'default' | 'fast' | 'complex' | 'local', cfg: ProviderConfig): void {
const models = (this.config.models ?? {}) as Record<string, unknown>;
const entry: Record<string, unknown> = { 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<string, unknown>;
server.port = port;
this.config.server = server;
}
setGatewayToken(token: string): void {
const server = (this.config.server ?? {}) as Record<string, unknown>;
server.token = token;
this.config.server = server;
}
setGatewayLock(enabled: boolean): void {
const server = (this.config.server ?? {}) as Record<string, unknown>;
server.lock = enabled;
this.config.server = server;
}
setTailscaleServe(enabled: boolean): void {
const server = (this.config.server ?? {}) as Record<string, unknown>;
const tailscale = (server.tailscale ?? {}) as Record<string, unknown>;
tailscale.serve = enabled;
server.tailscale = tailscale;
this.config.server = server;
}
setMemoryEmbedding(cfg: EmbeddingConfig): void {
const memory = (this.config.memory ?? {}) as Record<string, unknown>;
const embedding: Record<string, unknown> = { 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 };
}
setWebhooksEnabled(secret?: string): void {
const automation = (this.config.automation ?? {}) as Record<string, unknown>;
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<string, unknown>;
automation.gmail = { enabled: true, credentials_file: credentialsFile, output: { channel: outputChannel, peer: outputPeer } };
this.config.automation = automation;
}
setCronEnabled(): void {
const automation = (this.config.automation ?? {}) as Record<string, unknown>;
if (!automation.cron) automation.cron = [];
this.config.automation = automation;
}
build(): Record<string, any> {
return structuredClone(this.config) as Record<string, any>;
}
toYaml(): string {
return stringify(this.config, { lineWidth: 120 });
}
}