diff --git a/apps/web/src/app/(app)/gastown/[townId]/TownOverviewPageClient.tsx b/apps/web/src/app/(app)/gastown/[townId]/TownOverviewPageClient.tsx index c52983951e..67a2b76882 100644 --- a/apps/web/src/app/(app)/gastown/[townId]/TownOverviewPageClient.tsx +++ b/apps/web/src/app/(app)/gastown/[townId]/TownOverviewPageClient.tsx @@ -29,6 +29,7 @@ import { ChevronDown, Layers, MessageSquare, + Copy, } from 'lucide-react'; import { toast } from 'sonner'; import { formatDistanceToNow } from 'date-fns'; @@ -228,6 +229,17 @@ export function TownOverviewPageClient({ Live + { + void navigator.clipboard.writeText(townId); + toast.success('Copied town ID'); + }} + className="flex items-center gap-1 rounded px-1.5 py-0.5 font-mono text-xs text-white/30 hover:bg-white/[0.06] hover:text-white/60 transition-colors" + title={townId} + > + + {townId.slice(0, 8)}… + { + let git_url_sanitized: string | null = null; + if (r.git_url) { + try { + const u = new URL(r.git_url); + u.username = ''; + u.password = ''; + git_url_sanitized = u.toString(); + } catch { + // not a parseable URL — omit entirely to avoid leaking anything + } + } + return { + id: r.id, + name: r.name, + git_url: git_url_sanitized, + default_branch: r.default_branch, + }; + }), + + settings: cfg + ? { + default_model: cfg.default_model ?? null, + small_model: cfg.small_model ?? null, + role_models: { + mayor: cfg.role_models?.mayor ?? null, + refinery: cfg.role_models?.refinery ?? null, + polecat: cfg.role_models?.polecat ?? null, + }, + + max_polecats_per_rig: cfg.max_polecats_per_rig ?? null, + + github_token_set: !!(cfg.git_auth?.github_token), + gitlab_token_set: !!(cfg.git_auth?.gitlab_token), + gitlab_instance_url: cfg.git_auth?.gitlab_instance_url || null, + github_cli_pat_set: !!(cfg.github_cli_pat), + git_author_name_set: !!(cfg.git_author_name), + // git_author_name and git_author_email intentionally omitted (PII) + disable_ai_coauthor: cfg.disable_ai_coauthor ?? false, + + env_var_keys: Object.keys(cfg.env_vars ?? {}), + + merge_strategy: cfg.merge_strategy ?? 'direct', + convoy_merge_mode: cfg.convoy_merge_mode ?? 'review-then-land', + staged_convoys_default: cfg.staged_convoys_default ?? false, + + refinery: { + code_review: cfg.refinery?.code_review ?? true, + auto_merge: cfg.refinery?.auto_merge ?? true, + review_mode: cfg.refinery?.review_mode ?? 'rework', + auto_resolve_pr_feedback: cfg.refinery?.auto_resolve_pr_feedback ?? false, + auto_merge_delay_minutes: cfg.refinery?.auto_merge_delay_minutes ?? null, + gates: cfg.refinery?.gates ?? [], + }, + + custom_instructions: { + mayor_set: !!(cfg.custom_instructions?.mayor), + polecat_set: !!(cfg.custom_instructions?.polecat), + refinery_set: !!(cfg.custom_instructions?.refinery), + }, + + alarm_interval_active: cfg.alarm_interval_active ?? null, + alarm_interval_idle: cfg.alarm_interval_idle ?? null, + } + : null, + + generated_at: new Date().toISOString(), + url: window.location.href, + }; + + void navigator.clipboard.writeText(JSON.stringify(debugInfo, null, 2)); + toast.success('Debug info copied to clipboard'); + } + function addEnvVar() { setEnvVars(prev => [...prev, { key: '', value: '', isNew: true }]); } @@ -1147,13 +1232,38 @@ export function TownSettingsPageClient({ townId, readOnly = false, organizationI + {/* ── Debug ──────────────────────────────────────────── */} + + + + Copies a JSON snapshot of your town configuration for troubleshooting. API + tokens, email addresses, and custom instruction contents are excluded. + + + + Copy debug info + + + + {/* ── Danger Zone ──────────────────────────────────────── */}
+ Copies a JSON snapshot of your town configuration for troubleshooting. API + tokens, email addresses, and custom instruction contents are excluded. +