import { VERSION } from "../version.js"; import { ClawdisSchema } from "./config.js"; export type ConfigUiHint = { label?: string; help?: string; group?: string; order?: number; advanced?: boolean; sensitive?: boolean; placeholder?: string; itemTemplate?: unknown; }; export type ConfigUiHints = Record; export type ConfigSchema = ReturnType; export type ConfigSchemaResponse = { schema: ConfigSchema; uiHints: ConfigUiHints; version: string; generatedAt: string; }; const GROUP_LABELS: Record = { identity: "Identity", wizard: "Wizard", logging: "Logging", gateway: "Gateway", agent: "Agent", models: "Models", routing: "Routing", messages: "Messages", session: "Session", cron: "Cron", hooks: "Hooks", ui: "UI", browser: "Browser", talk: "Talk", telegram: "Telegram", discord: "Discord", signal: "Signal", imessage: "iMessage", whatsapp: "WhatsApp", skills: "Skills", discovery: "Discovery", presence: "Presence", voicewake: "Voice Wake", }; const GROUP_ORDER: Record = { identity: 10, wizard: 20, gateway: 30, agent: 40, models: 50, routing: 60, messages: 70, session: 80, cron: 90, hooks: 100, ui: 110, browser: 120, talk: 130, telegram: 140, discord: 150, signal: 160, imessage: 170, whatsapp: 180, skills: 190, discovery: 200, presence: 210, voicewake: 220, logging: 900, }; const FIELD_LABELS: Record = { "gateway.remote.url": "Remote Gateway URL", "gateway.remote.token": "Remote Gateway Token", "gateway.remote.password": "Remote Gateway Password", "gateway.auth.token": "Gateway Token", "gateway.auth.password": "Gateway Password", "gateway.controlUi.basePath": "Control UI Base Path", "agent.workspace": "Workspace", "agent.model": "Default Model", "ui.seamColor": "Accent Color", "browser.controlUrl": "Browser Control URL", "talk.apiKey": "Talk API Key", "telegram.botToken": "Telegram Bot Token", "discord.token": "Discord Bot Token", "signal.account": "Signal Account", "imessage.cliPath": "iMessage CLI Path", }; const FIELD_HELP: Record = { "gateway.remote.url": "Remote Gateway WebSocket URL (ws:// or wss://).", "gateway.auth.token": "Required for multi-machine access or non-loopback binds.", "gateway.auth.password": "Required for Tailscale funnel.", "gateway.controlUi.basePath": "Optional URL prefix where the Control UI is served (e.g. /clawdis).", }; const FIELD_PLACEHOLDERS: Record = { "gateway.remote.url": "ws://host:18789", "gateway.controlUi.basePath": "/clawdis", }; const SENSITIVE_PATTERNS = [/token/i, /password/i, /secret/i, /api.?key/i]; function isSensitivePath(path: string): boolean { return SENSITIVE_PATTERNS.some((pattern) => pattern.test(path)); } function buildBaseHints(): ConfigUiHints { const hints: ConfigUiHints = {}; for (const [group, label] of Object.entries(GROUP_LABELS)) { hints[group] = { label, group: label, order: GROUP_ORDER[group], }; } for (const [path, label] of Object.entries(FIELD_LABELS)) { hints[path] = { ...(hints[path] ?? {}), label }; } for (const [path, help] of Object.entries(FIELD_HELP)) { hints[path] = { ...(hints[path] ?? {}), help }; } for (const [path, placeholder] of Object.entries(FIELD_PLACEHOLDERS)) { hints[path] = { ...(hints[path] ?? {}), placeholder }; } return hints; } function applySensitiveHints(hints: ConfigUiHints): ConfigUiHints { const next = { ...hints }; for (const key of Object.keys(next)) { if (isSensitivePath(key)) { next[key] = { ...next[key], sensitive: true }; } } return next; } let cached: ConfigSchemaResponse | null = null; export function buildConfigSchema(): ConfigSchemaResponse { if (cached) return cached; const schema = ClawdisSchema.toJSONSchema({ target: "draft-07", unrepresentable: "any", }); schema.title = "ClawdisConfig"; const hints = applySensitiveHints(buildBaseHints()); const next = { schema, uiHints: hints, version: VERSION, generatedAt: new Date().toISOString(), }; cached = next; return next; }