@developer.krd/discord-dashboard 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/dashboard.ts","../src/discord-helpers.ts","../src/templates.ts","../src/templates/compact.ts","../src/templates/default.ts","../src/templates/index.ts","../src/designer.ts"],"sourcesContent":["import compression from \"compression\";\nimport express, { type Express, type NextFunction, type Request, type Response } from \"express\";\nimport session from \"express-session\";\nimport helmet from \"helmet\";\nimport { createServer, type Server } from \"node:http\";\nimport { randomBytes } from \"node:crypto\";\nimport { createDiscordHelpers } from \"./discord-helpers\";\nimport { renderDashboardHtml } from \"./templates\";\nimport { getBuiltinTemplateRenderer } from \"./templates/index\";\nimport type {\n DashboardCard,\n DashboardContext,\n DashboardGuild,\n DashboardTemplateRenderer,\n DashboardScope,\n HomeCategory,\n HomeSection,\n DashboardInstance,\n DashboardOptions,\n DashboardPlugin,\n DashboardUser,\n PluginActionResult\n} from \"./types\";\n\nconst DISCORD_API = \"https://discord.com/api/v10\";\nconst MANAGE_GUILD_PERMISSION = 0x20n;\nconst ADMIN_PERMISSION = 0x8n;\n\nfunction normalizeBasePath(basePath?: string): string {\n if (!basePath || basePath === \"/\") {\n return \"/dashboard\";\n }\n\n return basePath.startsWith(\"/\") ? basePath : `/${basePath}`;\n}\n\nfunction canManageGuild(permissions: string): boolean {\n const value = BigInt(permissions);\n return (value & MANAGE_GUILD_PERMISSION) === MANAGE_GUILD_PERMISSION || (value & ADMIN_PERMISSION) === ADMIN_PERMISSION;\n}\n\nfunction toQuery(params: Record<string, string>): string {\n const url = new URLSearchParams();\n for (const [key, value] of Object.entries(params)) {\n url.set(key, value);\n }\n return url.toString();\n}\n\nasync function fetchDiscord<T>(path: string, token: string): Promise<T> {\n const response = await fetch(`${DISCORD_API}${path}`, {\n headers: {\n Authorization: `Bearer ${token}`\n }\n });\n\n if (!response.ok) {\n throw new Error(`Discord API request failed (${response.status})`);\n }\n\n return (await response.json()) as T;\n}\n\nasync function exchangeCodeForToken(options: DashboardOptions, code: string): Promise<{\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n}> {\n const response = await fetch(`${DISCORD_API}/oauth2/token`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\"\n },\n body: toQuery({\n client_id: options.clientId,\n client_secret: options.clientSecret,\n grant_type: \"authorization_code\",\n code,\n redirect_uri: options.redirectUri\n })\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`Failed token exchange: ${response.status} ${text}`);\n }\n\n return (await response.json()) as {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n };\n}\n\nfunction createContext(req: Request, options: DashboardOptions): DashboardContext {\n const auth = req.session.discordAuth;\n if (!auth) {\n throw new Error(\"Not authenticated\");\n }\n\n const selectedGuildId = typeof req.query.guildId === \"string\" ? req.query.guildId : undefined;\n return {\n user: auth.user,\n guilds: auth.guilds,\n accessToken: auth.accessToken,\n selectedGuildId,\n helpers: createDiscordHelpers(options.botToken)\n };\n}\n\nfunction ensureAuthenticated(req: Request, res: Response, next: NextFunction): void {\n if (!req.session.discordAuth) {\n res.status(401).json({ authenticated: false, message: \"Authentication required\" });\n return;\n }\n\n next();\n}\n\nasync function resolveOverviewCards(options: DashboardOptions, context: DashboardContext): Promise<DashboardCard[]> {\n if (options.getOverviewCards) {\n return await options.getOverviewCards(context);\n }\n\n const manageableGuildCount = context.guilds.filter((guild) => guild.owner || canManageGuild(guild.permissions)).length;\n\n return [\n {\n id: \"user\",\n title: \"Logged-in User\",\n value: context.user.global_name || context.user.username,\n subtitle: `ID: ${context.user.id}`,\n intent: \"info\"\n },\n {\n id: \"guilds\",\n title: \"Manageable Guilds\",\n value: manageableGuildCount,\n subtitle: \"Owner or Manage Server permissions\",\n intent: \"success\"\n },\n {\n id: \"plugins\",\n title: \"Plugins Loaded\",\n value: options.plugins?.length ?? 0,\n subtitle: \"Dynamic server modules\",\n intent: \"neutral\"\n }\n ];\n}\n\nasync function resolveHomeSections(options: DashboardOptions, context: DashboardContext): Promise<HomeSection[]> {\n const customSections = options.home?.getSections ? await options.home.getSections(context) : [];\n const overviewSections = options.home?.getOverviewSections ? await options.home.getOverviewSections(context) : [];\n\n if (customSections.length > 0 || overviewSections.length > 0) {\n const normalizedOverview = overviewSections.map((section) => ({\n ...section,\n categoryId: section.categoryId ?? \"overview\"\n }));\n return [...normalizedOverview, ...customSections];\n }\n\n const selectedGuild = context.selectedGuildId\n ? context.guilds.find((guild) => guild.id === context.selectedGuildId)\n : undefined;\n\n return [\n {\n id: \"setup\",\n title: \"Setup Details\",\n description: \"Core dashboard setup information\",\n scope: \"setup\",\n categoryId: \"setup\",\n fields: [\n {\n id: \"dashboardName\",\n label: \"Dashboard Name\",\n type: \"text\",\n value: options.dashboardName ?? \"Discord Dashboard\",\n readOnly: true\n },\n {\n id: \"basePath\",\n label: \"Base Path\",\n type: \"text\",\n value: options.basePath ?? \"/dashboard\",\n readOnly: true\n }\n ]\n },\n {\n id: \"context\",\n title: \"Dashboard Context\",\n description: selectedGuild ? `Managing ${selectedGuild.name}` : \"Managing user dashboard\",\n scope: resolveScope(context),\n categoryId: \"overview\",\n fields: [\n {\n id: \"mode\",\n label: \"Mode\",\n type: \"text\",\n value: selectedGuild ? \"Guild\" : \"User\",\n readOnly: true\n },\n {\n id: \"target\",\n label: \"Target\",\n type: \"text\",\n value: selectedGuild ? selectedGuild.name : context.user.username,\n readOnly: true\n }\n ]\n }\n ];\n}\n\nfunction resolveScope(context: DashboardContext): DashboardScope {\n return context.selectedGuildId ? \"guild\" : \"user\";\n}\n\nasync function resolveHomeCategories(options: DashboardOptions, context: DashboardContext): Promise<HomeCategory[]> {\n if (options.home?.getCategories) {\n const categories = await options.home.getCategories(context);\n return [...categories].sort((a, b) => {\n if (a.id === \"overview\") return -1;\n if (b.id === \"overview\") return 1;\n return 0;\n });\n }\n\n return [\n { id: \"overview\", label: \"Overview\", scope: resolveScope(context) },\n { id: \"setup\", label: \"Setup\", scope: \"setup\" }\n ];\n}\n\nfunction getUserAvatarUrl(user: DashboardUser): string | null {\n if (user.avatar) {\n const ext = user.avatar.startsWith(\"a_\") ? \"gif\" : \"png\";\n return `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.${ext}?size=256`;\n }\n\n const fallbackIndex = Number((BigInt(user.id) >> 22n) % 6n);\n return `https://cdn.discordapp.com/embed/avatars/${fallbackIndex}.png`;\n}\n\nfunction getGuildIconUrl(guild: DashboardGuild): string | null {\n if (!guild.icon) {\n return null;\n }\n\n const ext = guild.icon.startsWith(\"a_\") ? \"gif\" : \"png\";\n return `https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}.${ext}?size=128`;\n}\n\nfunction createGuildInviteUrl(options: DashboardOptions, guildId: string): string {\n const scopes = options.botInviteScopes && options.botInviteScopes.length > 0\n ? options.botInviteScopes\n : [\"bot\", \"applications.commands\"];\n\n return `https://discord.com/oauth2/authorize?${toQuery({\n client_id: options.clientId,\n scope: scopes.join(\" \"),\n permissions: options.botInvitePermissions ?? \"8\",\n guild_id: guildId,\n disable_guild_select: \"true\"\n })}`;\n}\n\nasync function fetchBotGuildIds(botToken: string): Promise<Set<string>> {\n type BotGuild = { id: string };\n\n const response = await fetch(`${DISCORD_API}/users/@me/guilds`, {\n headers: {\n Authorization: `Bot ${botToken}`\n }\n });\n\n if (!response.ok) {\n return new Set();\n }\n\n const guilds = (await response.json()) as BotGuild[];\n return new Set(guilds.map((guild) => guild.id));\n}\n\nfunction resolveTemplateRenderer(options: DashboardOptions): DashboardTemplateRenderer {\n const selectedTemplate = options.uiTemplate ?? \"default\";\n const defaultRenderer: DashboardTemplateRenderer = ({ dashboardName, basePath, setupDesign }) =>\n renderDashboardHtml(dashboardName, basePath, setupDesign);\n\n const customRenderer = options.uiTemplates?.[selectedTemplate];\n if (customRenderer) {\n return customRenderer;\n }\n\n const builtinRenderer = getBuiltinTemplateRenderer(selectedTemplate);\n if (builtinRenderer) {\n return builtinRenderer;\n }\n\n if (selectedTemplate !== \"default\") {\n throw new Error(`Unknown uiTemplate '${selectedTemplate}'. Register it in uiTemplates.`);\n }\n\n return defaultRenderer;\n}\n\nexport function createDashboard(options: DashboardOptions): DashboardInstance {\n const app = options.app ?? express();\n const basePath = normalizeBasePath(options.basePath);\n const dashboardName = options.dashboardName ?? \"Discord Dashboard\";\n const templateRenderer = resolveTemplateRenderer(options);\n const plugins = options.plugins ?? [];\n\n if (!options.botToken) throw new Error(\"botToken is required\");\n if (!options.clientId) throw new Error(\"clientId is required\");\n if (!options.clientSecret) throw new Error(\"clientSecret is required\");\n if (!options.redirectUri) throw new Error(\"redirectUri is required\");\n if (!options.sessionSecret) throw new Error(\"sessionSecret is required\");\n\n if (!options.app && options.trustProxy !== undefined) {\n app.set(\"trust proxy\", options.trustProxy);\n }\n\n const router = express.Router();\n const sessionMiddleware = session({\n name: options.sessionName ?? \"discord_dashboard.sid\",\n secret: options.sessionSecret,\n resave: false,\n saveUninitialized: false,\n cookie: {\n httpOnly: true,\n sameSite: \"lax\",\n maxAge: options.sessionMaxAgeMs ?? 1000 * 60 * 60 * 24 * 7\n }\n });\n\n router.use(compression());\n router.use(\n helmet({\n contentSecurityPolicy: false\n })\n );\n router.use(express.json());\n router.use(sessionMiddleware);\n\n router.get(\"/\", (req, res) => {\n if (!req.session.discordAuth) {\n res.redirect(`${basePath}/login`);\n return;\n }\n\n res.setHeader(\"Cache-Control\", \"no-store\");\n res.type(\"html\").send(templateRenderer({\n dashboardName,\n basePath,\n setupDesign: options.setupDesign\n }));\n });\n\n router.get(\"/login\", (req, res) => {\n const state = randomBytes(16).toString(\"hex\");\n req.session.oauthState = state;\n\n const scope = (options.scopes && options.scopes.length > 0 ? options.scopes : [\"identify\", \"guilds\"]).join(\" \");\n\n const query = toQuery({\n client_id: options.clientId,\n redirect_uri: options.redirectUri,\n response_type: \"code\",\n scope,\n state,\n prompt: \"none\"\n });\n\n res.redirect(`https://discord.com/oauth2/authorize?${query}`);\n });\n\n router.get(\"/callback\", async (req, res) => {\n try {\n const code = typeof req.query.code === \"string\" ? req.query.code : undefined;\n const state = typeof req.query.state === \"string\" ? req.query.state : undefined;\n\n if (!code || !state) {\n res.status(400).send(\"Missing OAuth2 code/state\");\n return;\n }\n\n if (!req.session.oauthState || req.session.oauthState !== state) {\n res.status(403).send(\"Invalid OAuth2 state\");\n return;\n }\n\n const tokenData = await exchangeCodeForToken(options, code);\n const [user, guilds] = await Promise.all([\n fetchDiscord<DashboardUser>(\"/users/@me\", tokenData.access_token),\n fetchDiscord<DashboardGuild[]>(\"/users/@me/guilds\", tokenData.access_token)\n ]);\n\n req.session.discordAuth = {\n accessToken: tokenData.access_token,\n refreshToken: tokenData.refresh_token,\n expiresAt: tokenData.expires_in ? Date.now() + tokenData.expires_in * 1000 : undefined,\n user,\n guilds\n };\n\n req.session.oauthState = undefined;\n res.redirect(basePath);\n } catch (error) {\n const message = error instanceof Error ? error.message : \"OAuth callback failed\";\n res.status(500).send(message);\n }\n });\n\n router.post(\"/logout\", (req, res) => {\n req.session.destroy((sessionError) => {\n if (sessionError) {\n res.status(500).json({ ok: false, message: \"Failed to destroy session\" });\n return;\n }\n\n res.clearCookie(options.sessionName ?? \"discord_dashboard.sid\");\n res.json({ ok: true });\n });\n });\n\n router.get(\"/api/session\", (req, res) => {\n const auth = req.session.discordAuth;\n if (!auth) {\n res.status(200).json({ authenticated: false });\n return;\n }\n\n const manageableGuildCount = auth.guilds.filter((guild) => guild.owner || canManageGuild(guild.permissions)).length;\n\n res.json({\n authenticated: true,\n user: {\n ...auth.user,\n avatarUrl: getUserAvatarUrl(auth.user)\n },\n guildCount: manageableGuildCount,\n expiresAt: auth.expiresAt\n });\n });\n\n router.get(\"/api/guilds\", ensureAuthenticated, async (req, res) => {\n const context = createContext(req, options);\n\n if (options.ownerIds && options.ownerIds.length > 0 && !options.ownerIds.includes(context.user.id)) {\n res.status(403).json({ message: \"You are not allowed to access this dashboard.\" });\n return;\n }\n\n let manageableGuilds = context.guilds.filter((guild) => guild.owner || canManageGuild(guild.permissions));\n\n if (options.guildFilter) {\n const filtered: DashboardGuild[] = [];\n for (const guild of manageableGuilds) {\n const allowed = await options.guildFilter(guild, context);\n if (allowed) {\n filtered.push(guild);\n }\n }\n manageableGuilds = filtered;\n }\n\n const botGuildIds = await fetchBotGuildIds(options.botToken);\n const enrichedGuilds = manageableGuilds.map((guild) => {\n const botInGuild = botGuildIds.has(guild.id);\n return {\n ...guild,\n iconUrl: getGuildIconUrl(guild),\n botInGuild,\n inviteUrl: botInGuild ? undefined : createGuildInviteUrl(options, guild.id)\n };\n });\n\n res.json({ guilds: enrichedGuilds });\n });\n\n router.get(\"/api/overview\", ensureAuthenticated, async (req, res) => {\n const context = createContext(req, options);\n const cards = await resolveOverviewCards(options, context);\n res.json({ cards });\n });\n\n router.get(\"/api/home/categories\", ensureAuthenticated, async (req, res) => {\n const context = createContext(req, options);\n const activeScope = resolveScope(context);\n const categories = await resolveHomeCategories(options, context);\n const visible = categories.filter((item) => item.scope === \"setup\" || item.scope === activeScope);\n res.json({ categories: visible, activeScope });\n });\n\n router.get(\"/api/home\", ensureAuthenticated, async (req, res) => {\n const context = createContext(req, options);\n const activeScope = resolveScope(context);\n const categoryId = typeof req.query.categoryId === \"string\" ? req.query.categoryId : undefined;\n let sections = await resolveHomeSections(options, context);\n\n sections = sections.filter((section) => {\n const sectionScope = section.scope ?? activeScope;\n if (sectionScope !== \"setup\" && sectionScope !== activeScope) {\n return false;\n }\n\n if (!categoryId) {\n return true;\n }\n\n return section.categoryId === categoryId;\n });\n\n res.json({ sections, activeScope });\n });\n\n router.get(\"/api/lookup/roles\", ensureAuthenticated, async (req, res) => {\n const context = createContext(req, options);\n const guildId = typeof req.query.guildId === \"string\" && req.query.guildId.length > 0\n ? req.query.guildId\n : context.selectedGuildId;\n\n if (!guildId) {\n res.status(400).json({ message: \"guildId is required\" });\n return;\n }\n\n const query = typeof req.query.q === \"string\" ? req.query.q : \"\";\n const limit = typeof req.query.limit === \"string\" ? Number(req.query.limit) : undefined;\n const includeManaged = typeof req.query.includeManaged === \"string\"\n ? req.query.includeManaged === \"true\"\n : undefined;\n\n const roles = await context.helpers.searchGuildRoles(guildId, query, {\n limit: Number.isFinite(limit) ? limit : undefined,\n includeManaged\n });\n\n res.json({ roles });\n });\n\n router.get(\"/api/lookup/channels\", ensureAuthenticated, async (req, res) => {\n const context = createContext(req, options);\n const guildId = typeof req.query.guildId === \"string\" && req.query.guildId.length > 0\n ? req.query.guildId\n : context.selectedGuildId;\n\n if (!guildId) {\n res.status(400).json({ message: \"guildId is required\" });\n return;\n }\n\n const query = typeof req.query.q === \"string\" ? req.query.q : \"\";\n const limit = typeof req.query.limit === \"string\" ? Number(req.query.limit) : undefined;\n const nsfw = typeof req.query.nsfw === \"string\"\n ? req.query.nsfw === \"true\"\n : undefined;\n const channelTypes = typeof req.query.channelTypes === \"string\"\n ? req.query.channelTypes\n .split(\",\")\n .map((item) => Number(item.trim()))\n .filter((item) => Number.isFinite(item))\n : undefined;\n\n const channels = await context.helpers.searchGuildChannels(guildId, query, {\n limit: Number.isFinite(limit) ? limit : undefined,\n nsfw,\n channelTypes\n });\n\n res.json({ channels });\n });\n\n router.get(\"/api/lookup/members\", ensureAuthenticated, async (req, res) => {\n const context = createContext(req, options);\n const guildId = typeof req.query.guildId === \"string\" && req.query.guildId.length > 0\n ? req.query.guildId\n : context.selectedGuildId;\n\n if (!guildId) {\n res.status(400).json({ message: \"guildId is required\" });\n return;\n }\n\n const query = typeof req.query.q === \"string\" ? req.query.q : \"\";\n const limit = typeof req.query.limit === \"string\" ? Number(req.query.limit) : undefined;\n const members = await context.helpers.searchGuildMembers(guildId, query, {\n limit: Number.isFinite(limit) ? limit : undefined\n });\n\n res.json({ members });\n });\n\n router.post(\"/api/home/:actionId\", ensureAuthenticated, async (req, res) => {\n const context = createContext(req, options);\n const action = options.home?.actions?.[req.params.actionId];\n\n if (!action) {\n res.status(404).json({ ok: false, message: \"Home action not found\" });\n return;\n }\n\n const payload = req.body as { sectionId?: string; values?: Record<string, unknown> };\n if (!payload || typeof payload.sectionId !== \"string\" || !payload.values || typeof payload.values !== \"object\") {\n res.status(400).json({ ok: false, message: \"Invalid home action payload\" });\n return;\n }\n\n let result: PluginActionResult;\n try {\n result = await action(context, {\n sectionId: payload.sectionId,\n values: payload.values\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Home action failed\";\n res.status(500).json({ ok: false, message });\n return;\n }\n\n res.json(result);\n });\n\n router.get(\"/api/plugins\", ensureAuthenticated, async (req, res) => {\n const context = createContext(req, options);\n const activeScope = context.selectedGuildId ? \"guild\" : \"user\";\n const payload = [] as Array<{ id: string; name: string; description?: string; panels: unknown[] }>;\n\n for (const plugin of plugins) {\n const pluginScope = plugin.scope ?? \"both\";\n if (pluginScope !== \"both\" && pluginScope !== activeScope) {\n continue;\n }\n\n const panels = await plugin.getPanels(context);\n payload.push({\n id: plugin.id,\n name: plugin.name,\n description: plugin.description,\n panels\n });\n }\n\n res.json({ plugins: payload });\n });\n\n router.post(\"/api/plugins/:pluginId/:actionId\", ensureAuthenticated, async (req, res) => {\n const context = createContext(req, options);\n const plugin = plugins.find((item) => item.id === req.params.pluginId) as DashboardPlugin | undefined;\n\n if (!plugin) {\n res.status(404).json({ ok: false, message: \"Plugin not found\" });\n return;\n }\n\n const action = plugin.actions?.[req.params.actionId];\n if (!action) {\n res.status(404).json({ ok: false, message: \"Action not found\" });\n return;\n }\n\n let result: PluginActionResult;\n try {\n result = await action(context, req.body);\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Plugin action failed\";\n res.status(500).json({ ok: false, message });\n return;\n }\n\n res.json(result);\n });\n\n app.use(basePath, router);\n\n let server: Server | undefined;\n\n return {\n app,\n async start() {\n if (options.app) {\n return;\n }\n\n if (server) {\n return;\n }\n\n const port = options.port ?? 3000;\n const host = options.host ?? \"0.0.0.0\";\n\n server = createServer(app);\n await new Promise<void>((resolve) => {\n server!.listen(port, host, () => resolve());\n });\n },\n async stop() {\n if (!server) {\n return;\n }\n\n await new Promise<void>((resolve, reject) => {\n server!.close((error) => {\n if (error) {\n reject(error);\n return;\n }\n\n resolve();\n });\n });\n\n server = undefined;\n }\n };\n}\n","import type {\n DashboardDiscordHelpers,\n DiscordChannel,\n DiscordMember,\n DiscordRole\n} from \"./types\";\n\nconst DISCORD_API = \"https://discord.com/api/v10\";\n\nasync function fetchDiscordWithBot<T>(botToken: string, path: string): Promise<T | null> {\n const response = await fetch(`${DISCORD_API}${path}`, {\n headers: {\n Authorization: `Bot ${botToken}`\n }\n });\n\n if (!response.ok) {\n return null;\n }\n\n return (await response.json()) as T;\n}\n\nexport function createDiscordHelpers(botToken: string): DashboardDiscordHelpers {\n return {\n async getChannel(channelId: string): Promise<DiscordChannel | null> {\n return await fetchDiscordWithBot<DiscordChannel>(botToken, `/channels/${channelId}`);\n },\n\n async getGuildChannels(guildId: string): Promise<DiscordChannel[]> {\n return (await fetchDiscordWithBot<DiscordChannel[]>(botToken, `/guilds/${guildId}/channels`)) ?? [];\n },\n\n async searchGuildChannels(\n guildId: string,\n query: string,\n options?: { limit?: number; nsfw?: boolean; channelTypes?: number[] }\n ): Promise<DiscordChannel[]> {\n const channels = (await fetchDiscordWithBot<DiscordChannel[]>(botToken, `/guilds/${guildId}/channels`)) ?? [];\n const normalizedQuery = query.trim().toLowerCase();\n const limit = Math.max(1, Math.min(options?.limit ?? 10, 50));\n\n return channels\n .filter((channel) => {\n if (options?.nsfw !== undefined && Boolean(channel.nsfw) !== options.nsfw) {\n return false;\n }\n\n if (options?.channelTypes && options.channelTypes.length > 0 && !options.channelTypes.includes(channel.type)) {\n return false;\n }\n\n if (!normalizedQuery) {\n return true;\n }\n\n return channel.name.toLowerCase().includes(normalizedQuery);\n })\n .slice(0, limit);\n },\n\n async getRole(guildId: string, roleId: string): Promise<DiscordRole | null> {\n const roles = await fetchDiscordWithBot<DiscordRole[]>(botToken, `/guilds/${guildId}/roles`);\n if (!roles) {\n return null;\n }\n\n return roles.find((role) => role.id === roleId) ?? null;\n },\n\n async getGuildRoles(guildId: string): Promise<DiscordRole[]> {\n return (await fetchDiscordWithBot<DiscordRole[]>(botToken, `/guilds/${guildId}/roles`)) ?? [];\n },\n\n async searchGuildRoles(\n guildId: string,\n query: string,\n options?: { limit?: number; includeManaged?: boolean }\n ): Promise<DiscordRole[]> {\n const roles = (await fetchDiscordWithBot<DiscordRole[]>(botToken, `/guilds/${guildId}/roles`)) ?? [];\n const normalizedQuery = query.trim().toLowerCase();\n const limit = Math.max(1, Math.min(options?.limit ?? 10, 50));\n\n return roles\n .filter((role) => {\n if (!options?.includeManaged && role.managed) {\n return false;\n }\n\n if (!normalizedQuery) {\n return true;\n }\n\n return role.name.toLowerCase().includes(normalizedQuery);\n })\n .sort((a, b) => b.position - a.position)\n .slice(0, limit);\n },\n\n async searchGuildMembers(\n guildId: string,\n query: string,\n options?: { limit?: number }\n ): Promise<DiscordMember[]> {\n const limit = Math.max(1, Math.min(options?.limit ?? 10, 1000));\n const params = new URLSearchParams({\n query: query.trim(),\n limit: String(limit)\n });\n\n return (await fetchDiscordWithBot<DiscordMember[]>(botToken, `/guilds/${guildId}/members/search?${params.toString()}`)) ?? [];\n },\n\n async getGuildMember(guildId: string, userId: string): Promise<DiscordMember | null> {\n return await fetchDiscordWithBot<DiscordMember>(botToken, `/guilds/${guildId}/members/${userId}`);\n }\n };\n}\n","import type { DashboardDesignConfig } from \"./types\";\n\nconst appCss = `\n:root {\n color-scheme: dark;\n --bg: #13151a;\n --rail: #1e1f24;\n --content-bg: #2b2d31;\n --panel: #313338;\n --panel-2: #3a3d43;\n --text: #eef2ff;\n --muted: #b5bac1;\n --primary: #5865f2;\n --success: #20c997;\n --warning: #f4c95d;\n --danger: #ff6b6b;\n --info: #4dabf7;\n --border: rgba(255, 255, 255, 0.12);\n}\n* { box-sizing: border-box; }\nbody {\n margin: 0;\n font-family: Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;\n background: var(--bg);\n color: var(--text);\n}\n.layout {\n display: flex;\n min-height: 100vh;\n}\n.sidebar {\n width: 76px;\n background: var(--rail);\n padding: 12px 0;\n border-right: 1px solid var(--border);\n}\n.server-rail {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 10px;\n}\n.server-item {\n position: relative;\n width: 48px;\n height: 48px;\n border-radius: 50%;\n overflow: visible;\n border: none;\n padding: 0;\n background: var(--panel);\n color: #fff;\n font-weight: 700;\n display: grid;\n place-items: center;\n cursor: pointer;\n transition: border-radius .15s ease, background .15s ease, transform .15s ease;\n}\n.server-item:hover { border-radius: 16px; background: #404249; }\n.server-item.active {\n border-radius: 16px;\n background: var(--primary);\n transform: scale(1.1);\n}\n.server-item-indicator {\n position: absolute;\n left: -9px;\n width: 4px;\n height: 20px;\n border-radius: 999px;\n background: #fff;\n opacity: 0;\n transform: scaleY(0.5);\n transition: opacity .15s ease, transform .15s ease, height .15s ease;\n}\n.server-item.active .server-item-indicator {\n opacity: 1;\n transform: scaleY(1);\n height: 28px;\n}\n.server-avatar {\n width: 100%;\n height: 100%;\n object-fit: cover;\n object-position: center;\n display: block;\n border-radius: inherit;\n}\n.server-fallback {\n font-weight: 700;\n font-size: 0.8rem;\n}\n.main-tabs {\n display: flex;\n gap: 8px;\n margin-bottom: 14px;\n}\n.main-tab.active {\n background: var(--primary);\n border-color: transparent;\n}\n.server-status {\n position: absolute;\n right: -3px;\n bottom: -3px;\n width: 12px;\n height: 12px;\n border-radius: 999px;\n border: 2px solid var(--rail);\n background: #3ba55d;\n z-index: 2;\n}\n.server-status.offline {\n background: #747f8d;\n}\n.content {\n flex: 1;\n background: var(--content-bg);\n min-width: 0;\n}\n.topbar {\n display: grid;\n grid-template-columns: 1fr auto 1fr;\n align-items: center;\n padding: 14px 20px;\n border-bottom: 1px solid var(--border);\n}\n.brand {\n font-size: 1rem;\n font-weight: 700;\n}\n.center-title {\n text-align: center;\n font-weight: 700;\n font-size: 1rem;\n}\n.topbar-right {\n justify-self: end;\n}\n.container {\n padding: 22px;\n}\n.grid {\n display: grid;\n gap: 16px;\n}\n.cards { grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); }\n.panel {\n background: var(--panel);\n border: 1px solid var(--border);\n border-radius: 10px;\n padding: 16px;\n}\n.title { color: var(--muted); font-size: 0.9rem; }\n.value { font-size: 1.7rem; font-weight: 700; margin-top: 6px; }\n.subtitle { margin-top: 8px; color: var(--muted); font-size: 0.88rem; }\n.section-title { font-size: 1rem; margin: 20px 0 12px; color: #ffffff; }\n.pill {\n padding: 4px 9px;\n border-radius: 999px;\n font-size: 0.76rem;\n border: 1px solid var(--border);\n color: var(--muted);\n}\nbutton {\n border: 1px solid var(--border);\n background: var(--panel-2);\n color: var(--text);\n border-radius: 8px;\n padding: 8px 12px;\n cursor: pointer;\n}\nbutton.primary {\n background: var(--primary);\n border: none;\n}\nbutton.danger { background: #3a1e27; border-color: rgba(255,107,107,.45); }\n.actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }\n.home-categories {\n display: flex;\n gap: 8px;\n flex-wrap: wrap;\n margin-bottom: 12px;\n}\n.home-category-btn.active {\n background: var(--primary);\n border-color: transparent;\n}\n.home-sections {\n display: flex;\n flex-wrap: wrap;\n gap: 12px;\n margin-bottom: 12px;\n}\n.home-section-panel {\n flex: 0 0 100%;\n max-width: 100%;\n}\n.home-width-50 {\n flex-basis: calc(50% - 6px);\n max-width: calc(50% - 6px);\n}\n.home-width-33 {\n flex-basis: calc(33.333333% - 8px);\n max-width: calc(33.333333% - 8px);\n}\n.home-width-20 {\n flex-basis: calc(20% - 9.6px);\n max-width: calc(20% - 9.6px);\n}\n@media (max-width: 980px) {\n .home-width-50,\n .home-width-33,\n .home-width-20 {\n flex-basis: 100%;\n max-width: 100%;\n }\n}\n.home-fields { display: grid; gap: 10px; margin-top: 10px; }\n.home-field { display: grid; gap: 6px; }\n.home-field label { color: var(--muted); font-size: 0.84rem; }\n.lookup-wrap { position: relative; }\n.home-input,\n.home-textarea,\n.home-select {\n width: 100%;\n border: 1px solid var(--border);\n background: var(--panel-2);\n color: var(--text);\n border-radius: 8px;\n padding: 8px 10px;\n}\n.home-textarea { min-height: 92px; resize: vertical; }\n.home-checkbox {\n width: 18px;\n height: 18px;\n}\n.home-field-row {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.home-message {\n margin-top: 8px;\n color: var(--muted);\n font-size: 0.84rem;\n}\n.lookup-results {\n position: absolute;\n left: 0;\n right: 0;\n top: calc(100% + 6px);\n z-index: 20;\n border: 1px solid var(--border);\n background: var(--panel);\n border-radius: 8px;\n max-height: 220px;\n overflow: auto;\n display: none;\n}\n.lookup-item {\n width: 100%;\n border: none;\n border-radius: 0;\n text-align: left;\n padding: 8px 10px;\n background: transparent;\n}\n.lookup-item:hover {\n background: var(--panel-2);\n}\n.lookup-selected {\n margin-top: 6px;\n font-size: 0.82rem;\n color: var(--muted);\n}\n.kv { display: grid; gap: 8px; margin-top: 10px; }\n.kv-item {\n display: flex;\n justify-content: space-between;\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 8px 10px;\n background: var(--panel-2);\n}\n.plugin-fields {\n display: grid;\n gap: 10px;\n margin-top: 10px;\n}\n.plugin-field {\n display: grid;\n gap: 6px;\n}\n.plugin-field > label {\n color: var(--muted);\n font-size: 0.84rem;\n}\n.list-editor {\n border: 1px solid var(--border);\n border-radius: 8px;\n background: var(--panel-2);\n padding: 8px;\n display: grid;\n gap: 8px;\n}\n.list-items {\n display: grid;\n gap: 6px;\n}\n.list-item {\n display: grid;\n grid-template-columns: auto 1fr auto;\n gap: 8px;\n align-items: center;\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 6px 8px;\n background: var(--panel);\n}\n.list-item.dragging {\n opacity: .6;\n}\n.drag-handle {\n color: var(--muted);\n user-select: none;\n cursor: grab;\n font-size: 0.9rem;\n}\n.list-input {\n width: 100%;\n border: none;\n outline: none;\n background: transparent;\n color: var(--text);\n}\n.list-add {\n justify-self: start;\n}\n.empty { color: var(--muted); font-size: 0.9rem; }\n`;\n\nfunction escapeHtml(value: string): string {\n return value\n .replaceAll(\"&\", \"&amp;\")\n .replaceAll(\"<\", \"&lt;\")\n .replaceAll(\">\", \"&gt;\")\n .replaceAll('\"', \"&quot;\")\n .replaceAll(\"'\", \"&#039;\");\n}\n\nexport function renderDashboardHtml(name: string, basePath: string, setupDesign?: DashboardDesignConfig): string {\n const safeName = escapeHtml(name);\n const scriptData = JSON.stringify({ basePath, setupDesign: setupDesign ?? {} });\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>${safeName}</title>\n <style>${appCss}</style>\n</head>\n<body>\n <div class=\"layout\">\n <aside class=\"sidebar\">\n <div id=\"serverRail\" class=\"server-rail\"></div>\n </aside>\n\n <main class=\"content\">\n <header class=\"topbar\">\n <div class=\"brand\">${safeName}</div>\n <div id=\"centerTitle\" class=\"center-title\">User Dashboard</div>\n <div id=\"userMeta\" class=\"pill topbar-right\">Loading...</div>\n </header>\n\n <div class=\"container\">\n <div class=\"main-tabs\">\n <button id=\"tabHome\" class=\"main-tab active\">Home</button>\n <button id=\"tabPlugins\" class=\"main-tab\">Plugins</button>\n </div>\n\n <section id=\"homeArea\">\n <div class=\"section-title\">Home</div>\n <section id=\"homeCategories\" class=\"home-categories\"></section>\n <section id=\"homeSections\" class=\"home-sections\"></section>\n\n <section id=\"overviewArea\">\n <div class=\"section-title\">Dashboard Stats</div>\n <section id=\"overviewCards\" class=\"grid cards\"></section>\n </section>\n </section>\n\n <section id=\"pluginsArea\" style=\"display:none;\">\n <div class=\"section-title\">Plugins</div>\n <section id=\"plugins\" class=\"grid\"></section>\n </section>\n </div>\n </main>\n </div>\n\n <script>\n const dashboardConfig = ${scriptData};\n const state = {\n session: null,\n guilds: [],\n selectedGuildId: null,\n homeCategories: [],\n selectedHomeCategoryId: null,\n activeMainTab: \"home\"\n };\n\n const el = {\n serverRail: document.getElementById(\"serverRail\"),\n userMeta: document.getElementById(\"userMeta\"),\n centerTitle: document.getElementById(\"centerTitle\"),\n tabHome: document.getElementById(\"tabHome\"),\n tabPlugins: document.getElementById(\"tabPlugins\"),\n homeArea: document.getElementById(\"homeArea\"),\n pluginsArea: document.getElementById(\"pluginsArea\"),\n homeCategories: document.getElementById(\"homeCategories\"),\n homeSections: document.getElementById(\"homeSections\"),\n overviewArea: document.getElementById(\"overviewArea\"),\n overviewCards: document.getElementById(\"overviewCards\"),\n plugins: document.getElementById(\"plugins\")\n };\n\n const fetchJson = async (url, init) => {\n const response = await fetch(url, init);\n if (!response.ok) {\n throw new Error(await response.text());\n }\n return response.json();\n };\n\n const buildApiUrl = (path) => {\n if (!state.selectedGuildId) {\n return dashboardConfig.basePath + path;\n }\n\n const separator = path.includes(\"?\") ? \"&\" : \"?\";\n return dashboardConfig.basePath + path + separator + \"guildId=\" + encodeURIComponent(state.selectedGuildId);\n };\n\n const escapeHtml = (value) => String(value)\n .replaceAll(\"&\", \"&amp;\")\n .replaceAll(\"<\", \"&lt;\")\n .replaceAll(\">\", \"&gt;\")\n .replaceAll('\"', \"&quot;\")\n .replaceAll(\"'\", \"&#039;\");\n\n const normalizeBoxWidth = (value) => {\n const numeric = Number(value);\n if (numeric === 50 || numeric === 33 || numeric === 20) {\n return numeric;\n }\n\n return 100;\n };\n\n const applySetupDesign = () => {\n const root = document.documentElement;\n const design = dashboardConfig.setupDesign || {};\n const mappings = {\n bg: \"--bg\",\n rail: \"--rail\",\n contentBg: \"--content-bg\",\n panel: \"--panel\",\n panel2: \"--panel-2\",\n text: \"--text\",\n muted: \"--muted\",\n primary: \"--primary\",\n success: \"--success\",\n warning: \"--warning\",\n danger: \"--danger\",\n info: \"--info\",\n border: \"--border\"\n };\n\n Object.entries(mappings).forEach(([key, cssVar]) => {\n const value = design[key];\n if (typeof value === \"string\" && value.trim().length > 0) {\n root.style.setProperty(cssVar, value.trim());\n }\n });\n };\n\n const makeButton = (action, pluginId, panelId, panelElement) => {\n const button = document.createElement(\"button\");\n button.textContent = action.label;\n button.className = action.variant === \"primary\" ? \"primary\" : action.variant === \"danger\" ? \"danger\" : \"\";\n button.addEventListener(\"click\", async () => {\n button.disabled = true;\n try {\n let payload = {};\n if (action.collectFields && panelElement) {\n const values = {};\n const inputs = panelElement.querySelectorAll(\"[data-plugin-field-id]\");\n inputs.forEach((inputEl) => {\n const fieldId = inputEl.dataset.pluginFieldId;\n const fieldType = inputEl.dataset.pluginFieldType || \"text\";\n if (!fieldId) {\n return;\n }\n\n values[fieldId] = toFieldValue({ type: fieldType }, inputEl);\n });\n payload = {\n panelId,\n values\n };\n }\n\n const actionUrl = buildApiUrl(\"/api/plugins/\" + encodeURIComponent(pluginId) + \"/\" + encodeURIComponent(action.id));\n const result = await fetchJson(actionUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(payload)\n });\n if (result.message) {\n alert(result.message);\n }\n if (result.refresh) {\n await refreshContent();\n }\n } catch (error) {\n alert(error instanceof Error ? error.message : \"Action failed\");\n } finally {\n button.disabled = false;\n }\n });\n return button;\n };\n\n const renderCards = (cards) => {\n if (!cards.length) {\n el.overviewCards.innerHTML = '<div class=\"empty\">No cards configured yet.</div>';\n return;\n }\n\n el.overviewCards.innerHTML = cards.map((card) => {\n const subtitle = card.subtitle ? '<div class=\"subtitle\">' + escapeHtml(card.subtitle) + '</div>' : \"\";\n return '<article class=\"panel\">'\n + '<div class=\"title\">' + escapeHtml(card.title) + '</div>'\n + '<div class=\"value\">' + escapeHtml(card.value) + '</div>'\n + subtitle\n + '</article>';\n }).join(\"\");\n };\n\n const shortName = (name) => {\n if (!name) return \"?\";\n const parts = String(name).trim().split(/\\s+/).filter(Boolean);\n if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();\n return (parts[0][0] + parts[1][0]).toUpperCase();\n };\n\n const addAvatarOrFallback = (button, item) => {\n if (!item.avatarUrl) {\n const fallback = document.createElement(\"span\");\n fallback.className = \"server-fallback\";\n fallback.textContent = item.short;\n button.appendChild(fallback);\n return;\n }\n\n const avatar = document.createElement(\"img\");\n avatar.className = \"server-avatar\";\n avatar.src = item.avatarUrl;\n avatar.alt = item.name;\n avatar.addEventListener(\"error\", () => {\n avatar.remove();\n const fallback = document.createElement(\"span\");\n fallback.className = \"server-fallback\";\n fallback.textContent = item.short;\n button.appendChild(fallback);\n });\n button.appendChild(avatar);\n };\n\n const renderServerRail = () => {\n const items = [{ id: null, name: \"User Dashboard\", short: \"ME\", avatarUrl: state.session?.user?.avatarUrl ?? null, botInGuild: true }].concat(\n state.guilds.map((guild) => ({\n id: guild.id,\n name: guild.name,\n short: shortName(guild.name),\n avatarUrl: guild.iconUrl ?? null,\n botInGuild: guild.botInGuild !== false,\n inviteUrl: guild.inviteUrl\n }))\n );\n\n el.serverRail.innerHTML = \"\";\n items.forEach((item) => {\n const button = document.createElement(\"button\");\n button.className = \"server-item\" + (item.id === state.selectedGuildId ? \" active\" : \"\");\n button.title = item.id && !item.botInGuild ? (item.name + \" • Invite bot\") : item.name;\n\n const activeIndicator = document.createElement(\"span\");\n activeIndicator.className = \"server-item-indicator\";\n button.appendChild(activeIndicator);\n\n addAvatarOrFallback(button, item);\n\n if (item.id) {\n const status = document.createElement(\"span\");\n status.className = \"server-status\" + (item.botInGuild ? \"\" : \" offline\");\n button.appendChild(status);\n }\n\n button.addEventListener(\"click\", async () => {\n if (item.id && !item.botInGuild && item.inviteUrl) {\n const opened = window.open(item.inviteUrl, \"_blank\", \"noopener,noreferrer\");\n if (!opened && typeof alert === \"function\") {\n alert(\"Popup blocked. Please allow popups to open the invite page.\");\n }\n return;\n }\n\n state.selectedGuildId = item.id;\n renderServerRail();\n updateContextLabel();\n await refreshContent();\n });\n el.serverRail.appendChild(button);\n });\n };\n\n const applyMainTab = () => {\n const homeActive = state.activeMainTab === \"home\";\n el.homeArea.style.display = homeActive ? \"block\" : \"none\";\n el.pluginsArea.style.display = homeActive ? \"none\" : \"block\";\n el.tabHome.className = \"main-tab\" + (homeActive ? \" active\" : \"\");\n el.tabPlugins.className = \"main-tab\" + (!homeActive ? \" active\" : \"\");\n };\n\n const updateContextLabel = () => {\n if (!state.selectedGuildId) {\n el.centerTitle.textContent = \"User Dashboard\";\n return;\n }\n\n const selectedGuild = state.guilds.find((guild) => guild.id === state.selectedGuildId);\n el.centerTitle.textContent = selectedGuild ? (selectedGuild.name + \" Dashboard\") : \"Server Dashboard\";\n };\n\n const renderHomeCategories = () => {\n if (!state.homeCategories.length) {\n el.homeCategories.innerHTML = \"\";\n return;\n }\n\n el.homeCategories.innerHTML = \"\";\n state.homeCategories.forEach((category) => {\n const button = document.createElement(\"button\");\n button.className = \"home-category-btn\" + (state.selectedHomeCategoryId === category.id ? \" active\" : \"\");\n button.textContent = category.label;\n button.title = category.description || category.label;\n button.addEventListener(\"click\", async () => {\n state.selectedHomeCategoryId = category.id;\n renderHomeCategories();\n await refreshContent();\n });\n el.homeCategories.appendChild(button);\n });\n };\n\n const toFieldValue = (field, element) => {\n if (field.type === \"string-list\") {\n const raw = element.dataset.listValues;\n if (!raw) {\n return [];\n }\n\n try {\n const parsed = JSON.parse(raw);\n return Array.isArray(parsed) ? parsed : [];\n } catch {\n return [];\n }\n }\n\n if (field.type === \"role-search\" || field.type === \"channel-search\" || field.type === \"member-search\") {\n const raw = element.dataset.selectedObject;\n if (!raw) {\n return null;\n }\n\n try {\n return JSON.parse(raw);\n } catch {\n return null;\n }\n }\n\n if (field.type === \"boolean\") {\n return Boolean(element.checked);\n }\n\n if (field.type === \"number\") {\n if (element.value === \"\") {\n return null;\n }\n return Number(element.value);\n }\n\n return element.value;\n };\n\n const setupStringListField = (field, input, fieldWrap) => {\n const editor = document.createElement(\"div\");\n editor.className = \"list-editor\";\n const itemsWrap = document.createElement(\"div\");\n itemsWrap.className = \"list-items\";\n const addButton = document.createElement(\"button\");\n addButton.type = \"button\";\n addButton.className = \"list-add\";\n addButton.textContent = \"Add Button\";\n\n const normalizeValues = () => {\n const values = Array.from(itemsWrap.querySelectorAll(\".list-input\"))\n .map((item) => item.value.trim())\n .filter((item) => item.length > 0);\n input.dataset.listValues = JSON.stringify(values);\n };\n\n const makeRow = (value = \"\") => {\n const row = document.createElement(\"div\");\n row.className = \"list-item\";\n row.draggable = true;\n\n const handle = document.createElement(\"span\");\n handle.className = \"drag-handle\";\n handle.textContent = \"⋮⋮\";\n\n const textInput = document.createElement(\"input\");\n textInput.type = \"text\";\n textInput.className = \"list-input\";\n textInput.value = value;\n textInput.placeholder = \"Button label\";\n textInput.addEventListener(\"input\", normalizeValues);\n\n const removeButton = document.createElement(\"button\");\n removeButton.type = \"button\";\n removeButton.textContent = \"×\";\n removeButton.addEventListener(\"click\", () => {\n row.remove();\n normalizeValues();\n });\n\n row.addEventListener(\"dragstart\", () => {\n row.classList.add(\"dragging\");\n });\n row.addEventListener(\"dragend\", () => {\n row.classList.remove(\"dragging\");\n normalizeValues();\n });\n\n row.appendChild(handle);\n row.appendChild(textInput);\n row.appendChild(removeButton);\n return row;\n };\n\n itemsWrap.addEventListener(\"dragover\", (event) => {\n event.preventDefault();\n const dragging = itemsWrap.querySelector(\".dragging\");\n if (!dragging) {\n return;\n }\n\n const siblings = Array.from(itemsWrap.querySelectorAll(\".list-item:not(.dragging)\"));\n let inserted = false;\n\n for (const sibling of siblings) {\n const rect = sibling.getBoundingClientRect();\n if (event.clientY < rect.top + rect.height / 2) {\n itemsWrap.insertBefore(dragging, sibling);\n inserted = true;\n break;\n }\n }\n\n if (!inserted) {\n itemsWrap.appendChild(dragging);\n }\n });\n\n const initialValues = Array.isArray(field.value)\n ? field.value.map((item) => String(item))\n : [];\n if (initialValues.length === 0) {\n initialValues.push(\"Yes\", \"No\");\n }\n\n initialValues.forEach((value) => {\n itemsWrap.appendChild(makeRow(value));\n });\n\n addButton.addEventListener(\"click\", () => {\n itemsWrap.appendChild(makeRow(\"\"));\n normalizeValues();\n });\n\n editor.appendChild(itemsWrap);\n editor.appendChild(addButton);\n fieldWrap.appendChild(editor);\n normalizeValues();\n };\n\n const showLookupResults = (container, items, labelResolver, onSelect) => {\n container.innerHTML = \"\";\n\n if (!items.length) {\n container.style.display = \"none\";\n return;\n }\n\n items.forEach((item) => {\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.className = \"lookup-item\";\n btn.textContent = labelResolver(item);\n btn.addEventListener(\"click\", () => {\n onSelect(item);\n container.style.display = \"none\";\n });\n container.appendChild(btn);\n });\n\n container.style.display = \"block\";\n };\n\n const setupLookupField = (field, input, fieldWrap) => {\n const wrap = document.createElement(\"div\");\n wrap.className = \"lookup-wrap\";\n const results = document.createElement(\"div\");\n results.className = \"lookup-results\";\n const selected = document.createElement(\"div\");\n selected.className = \"lookup-selected\";\n selected.textContent = \"No selection\";\n\n wrap.appendChild(input);\n wrap.appendChild(results);\n fieldWrap.appendChild(wrap);\n fieldWrap.appendChild(selected);\n\n const minQueryLength = Math.max(0, field.lookup?.minQueryLength ?? 1);\n const limit = Math.max(1, Math.min(field.lookup?.limit ?? 10, 50));\n\n const runSearch = async () => {\n const query = String(input.value || \"\");\n if (query.length < minQueryLength) {\n results.style.display = \"none\";\n return;\n }\n\n try {\n if (field.type === \"role-search\") {\n const params = new URLSearchParams({ q: query, limit: String(limit) });\n if (field.lookup?.includeManaged !== undefined) {\n params.set(\"includeManaged\", String(Boolean(field.lookup.includeManaged)));\n }\n\n const payload = await fetchJson(buildApiUrl(\"/api/lookup/roles?\" + params.toString()));\n showLookupResults(\n results,\n payload.roles || [],\n (item) => \"@\" + item.name,\n (item) => {\n input.value = item.name;\n input.dataset.selectedObject = JSON.stringify(item);\n selected.textContent = \"Selected role: @\" + item.name + \" (\" + item.id + \")\";\n }\n );\n } else if (field.type === \"channel-search\") {\n const params = new URLSearchParams({ q: query, limit: String(limit) });\n if (field.lookup?.nsfw !== undefined) {\n params.set(\"nsfw\", String(Boolean(field.lookup.nsfw)));\n }\n if (field.lookup?.channelTypes && field.lookup.channelTypes.length > 0) {\n params.set(\"channelTypes\", field.lookup.channelTypes.join(\",\"));\n }\n\n const payload = await fetchJson(buildApiUrl(\"/api/lookup/channels?\" + params.toString()));\n showLookupResults(\n results,\n payload.channels || [],\n (item) => \"#\" + item.name,\n (item) => {\n input.value = item.name;\n input.dataset.selectedObject = JSON.stringify(item);\n selected.textContent = \"Selected channel: #\" + item.name + \" (\" + item.id + \")\";\n }\n );\n } else if (field.type === \"member-search\") {\n const params = new URLSearchParams({ q: query, limit: String(limit) });\n const payload = await fetchJson(buildApiUrl(\"/api/lookup/members?\" + params.toString()));\n showLookupResults(\n results,\n payload.members || [],\n (item) => {\n const username = item?.user?.username || \"unknown\";\n const nick = item?.nick ? \" (\" + item.nick + \")\" : \"\";\n return username + nick;\n },\n (item) => {\n const username = item?.user?.username || \"unknown\";\n input.value = username;\n input.dataset.selectedObject = JSON.stringify(item);\n selected.textContent = \"Selected member: \" + username + \" (\" + (item?.user?.id || \"unknown\") + \")\";\n }\n );\n }\n } catch {\n results.style.display = \"none\";\n }\n };\n\n input.addEventListener(\"input\", () => {\n input.dataset.selectedObject = \"\";\n selected.textContent = \"No selection\";\n runSearch();\n });\n\n input.addEventListener(\"blur\", () => {\n setTimeout(() => {\n results.style.display = \"none\";\n }, 120);\n });\n };\n\n const renderHomeSections = (sections) => {\n if (!sections.length) {\n el.homeSections.innerHTML = '<div class=\"empty\">No home sections configured.</div>';\n return;\n }\n\n el.homeSections.innerHTML = \"\";\n sections.forEach((section) => {\n const wrap = document.createElement(\"article\");\n wrap.className = \"panel home-section-panel home-width-\" + normalizeBoxWidth(section.width);\n\n const heading = document.createElement(\"h3\");\n heading.textContent = section.title;\n heading.style.margin = \"0\";\n wrap.appendChild(heading);\n\n if (section.description) {\n const desc = document.createElement(\"div\");\n desc.className = \"subtitle\";\n desc.textContent = section.description;\n wrap.appendChild(desc);\n }\n\n const message = document.createElement(\"div\");\n message.className = \"home-message\";\n\n if (section.fields?.length) {\n const fieldsWrap = document.createElement(\"div\");\n fieldsWrap.className = \"home-fields\";\n\n section.fields.forEach((field) => {\n const fieldWrap = document.createElement(\"div\");\n fieldWrap.className = \"home-field\";\n\n const label = document.createElement(\"label\");\n label.textContent = field.label;\n fieldWrap.appendChild(label);\n\n let input;\n if (field.type === \"textarea\") {\n input = document.createElement(\"textarea\");\n input.className = \"home-textarea\";\n input.value = field.value == null ? \"\" : String(field.value);\n } else if (field.type === \"select\") {\n input = document.createElement(\"select\");\n input.className = \"home-select\";\n (field.options || []).forEach((option) => {\n const optionEl = document.createElement(\"option\");\n optionEl.value = option.value;\n optionEl.textContent = option.label;\n if (String(field.value ?? \"\") === option.value) {\n optionEl.selected = true;\n }\n input.appendChild(optionEl);\n });\n } else if (field.type === \"boolean\") {\n const row = document.createElement(\"div\");\n row.className = \"home-field-row\";\n input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.className = \"home-checkbox\";\n input.checked = Boolean(field.value);\n const stateText = document.createElement(\"span\");\n stateText.textContent = input.checked ? \"Enabled\" : \"Disabled\";\n input.addEventListener(\"change\", () => {\n stateText.textContent = input.checked ? \"Enabled\" : \"Disabled\";\n });\n row.appendChild(input);\n row.appendChild(stateText);\n fieldWrap.appendChild(row);\n } else {\n input = document.createElement(\"input\");\n input.className = \"home-input\";\n input.type = field.type === \"number\" ? \"number\" : \"text\";\n input.value = field.value == null ? \"\" : String(field.value);\n }\n\n if (input) {\n input.dataset.homeFieldId = field.id;\n input.dataset.homeFieldType = field.type;\n if (field.placeholder && \"placeholder\" in input) {\n input.placeholder = field.placeholder;\n }\n if (field.required && \"required\" in input) {\n input.required = true;\n }\n if (field.readOnly) {\n if (\"readOnly\" in input) {\n input.readOnly = true;\n }\n if (\"disabled\" in input) {\n input.disabled = true;\n }\n }\n\n if (field.type === \"role-search\" || field.type === \"channel-search\" || field.type === \"member-search\") {\n setupLookupField(field, input, fieldWrap);\n } else if (field.type !== \"boolean\") {\n fieldWrap.appendChild(input);\n }\n }\n\n fieldsWrap.appendChild(fieldWrap);\n });\n\n wrap.appendChild(fieldsWrap);\n }\n\n if (section.actions?.length) {\n const actions = document.createElement(\"div\");\n actions.className = \"actions\";\n\n section.actions.forEach((action) => {\n const button = document.createElement(\"button\");\n button.textContent = action.label;\n button.className = action.variant === \"primary\" ? \"primary\" : action.variant === \"danger\" ? \"danger\" : \"\";\n button.addEventListener(\"click\", async () => {\n button.disabled = true;\n try {\n const values = {};\n const inputs = wrap.querySelectorAll(\"[data-home-field-id]\");\n inputs.forEach((inputEl) => {\n const fieldId = inputEl.dataset.homeFieldId;\n const fieldType = inputEl.dataset.homeFieldType || \"text\";\n if (!fieldId) {\n return;\n }\n\n values[fieldId] = toFieldValue({ type: fieldType }, inputEl);\n });\n\n const result = await fetchJson(buildApiUrl(\"/api/home/\" + encodeURIComponent(action.id)), {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n sectionId: section.id,\n values\n })\n });\n\n message.textContent = result.message || \"Saved.\";\n if (result.refresh) {\n await refreshContent();\n }\n } catch (error) {\n message.textContent = error instanceof Error ? error.message : \"Save failed\";\n } finally {\n button.disabled = false;\n }\n });\n actions.appendChild(button);\n });\n\n wrap.appendChild(actions);\n }\n\n wrap.appendChild(message);\n el.homeSections.appendChild(wrap);\n });\n };\n\n const renderPlugins = (plugins) => {\n if (!plugins.length) {\n el.plugins.innerHTML = '<div class=\"empty\">No plugins configured yet.</div>';\n return;\n }\n\n el.plugins.innerHTML = \"\";\n plugins.forEach((plugin) => {\n const wrap = document.createElement(\"article\");\n wrap.className = \"panel\";\n\n const heading = document.createElement(\"div\");\n heading.className = \"title\";\n heading.textContent = plugin.name;\n wrap.appendChild(heading);\n\n if (plugin.description) {\n const desc = document.createElement(\"div\");\n desc.className = \"subtitle\";\n desc.textContent = plugin.description;\n wrap.appendChild(desc);\n }\n\n (plugin.panels || []).forEach((panel) => {\n const panelBody = document.createElement(\"div\");\n\n const panelTitle = document.createElement(\"h4\");\n panelTitle.textContent = panel.title;\n panelTitle.style.marginBottom = \"4px\";\n panelBody.appendChild(panelTitle);\n\n if (panel.description) {\n const p = document.createElement(\"div\");\n p.className = \"subtitle\";\n p.textContent = panel.description;\n panelBody.appendChild(p);\n }\n\n if (panel.fields?.length) {\n const fieldsWrap = document.createElement(\"div\");\n fieldsWrap.className = \"plugin-fields\";\n\n panel.fields.forEach((field) => {\n const fieldWrap = document.createElement(\"div\");\n fieldWrap.className = field.editable ? \"plugin-field\" : \"kv-item\";\n\n if (!field.editable) {\n const display = field.value == null\n ? \"\"\n : typeof field.value === \"object\"\n ? JSON.stringify(field.value)\n : String(field.value);\n fieldWrap.innerHTML = '<strong>' + escapeHtml(field.label) + '</strong><span>' + escapeHtml(display) + '</span>';\n fieldsWrap.appendChild(fieldWrap);\n return;\n }\n\n const label = document.createElement(\"label\");\n label.textContent = field.label;\n fieldWrap.appendChild(label);\n\n let input;\n if (field.type === \"textarea\") {\n input = document.createElement(\"textarea\");\n input.className = \"home-textarea\";\n input.value = field.value == null ? \"\" : String(field.value);\n } else if (field.type === \"select\") {\n input = document.createElement(\"select\");\n input.className = \"home-select\";\n (field.options || []).forEach((option) => {\n const optionEl = document.createElement(\"option\");\n optionEl.value = option.value;\n optionEl.textContent = option.label;\n if (String(field.value ?? \"\") === option.value) {\n optionEl.selected = true;\n }\n input.appendChild(optionEl);\n });\n } else if (field.type === \"boolean\") {\n const row = document.createElement(\"div\");\n row.className = \"home-field-row\";\n input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.className = \"home-checkbox\";\n input.checked = Boolean(field.value);\n const stateText = document.createElement(\"span\");\n stateText.textContent = input.checked ? \"Enabled\" : \"Disabled\";\n input.addEventListener(\"change\", () => {\n stateText.textContent = input.checked ? \"Enabled\" : \"Disabled\";\n });\n row.appendChild(input);\n row.appendChild(stateText);\n fieldWrap.appendChild(row);\n } else {\n input = document.createElement(\"input\");\n input.className = \"home-input\";\n input.type = field.type === \"number\" ? \"number\" : field.type === \"url\" ? \"url\" : \"text\";\n input.value = field.value == null ? \"\" : String(field.value);\n }\n\n if (input) {\n input.dataset.pluginFieldId = field.id || field.label;\n input.dataset.pluginFieldType = field.type || \"text\";\n if (field.placeholder && \"placeholder\" in input) {\n input.placeholder = field.placeholder;\n }\n if (field.required && \"required\" in input) {\n input.required = true;\n }\n\n const isLookup = field.type === \"role-search\" || field.type === \"channel-search\" || field.type === \"member-search\";\n if (isLookup) {\n setupLookupField(field, input, fieldWrap);\n } else if (field.type === \"string-list\") {\n setupStringListField(field, input, fieldWrap);\n } else if (field.type !== \"boolean\") {\n fieldWrap.appendChild(input);\n }\n }\n\n fieldsWrap.appendChild(fieldWrap);\n });\n panelBody.appendChild(fieldsWrap);\n }\n\n if (panel.actions?.length) {\n const actions = document.createElement(\"div\");\n actions.className = \"actions\";\n panel.actions.forEach((action) => {\n actions.appendChild(makeButton(action, plugin.id, panel.id, panelBody));\n });\n panelBody.appendChild(actions);\n }\n\n wrap.appendChild(panelBody);\n });\n\n el.plugins.appendChild(wrap);\n });\n };\n\n const refreshContent = async () => {\n const categoriesPayload = await fetchJson(buildApiUrl(\"/api/home/categories\"));\n state.homeCategories = categoriesPayload.categories || [];\n if (!state.selectedHomeCategoryId || !state.homeCategories.some((item) => item.id === state.selectedHomeCategoryId)) {\n const overviewCategory = state.homeCategories.find((item) => item.id === \"overview\");\n state.selectedHomeCategoryId = overviewCategory ? overviewCategory.id : (state.homeCategories[0]?.id ?? null);\n }\n renderHomeCategories();\n\n const homePath = state.selectedHomeCategoryId\n ? \"/api/home?categoryId=\" + encodeURIComponent(state.selectedHomeCategoryId)\n : \"/api/home\";\n\n const [home, overview, plugins] = await Promise.all([\n fetchJson(buildApiUrl(homePath)),\n fetchJson(buildApiUrl(\"/api/overview\")),\n fetchJson(buildApiUrl(\"/api/plugins\"))\n ]);\n\n renderHomeSections(home.sections || []);\n const showOverviewArea = state.selectedHomeCategoryId === \"overview\";\n el.overviewArea.style.display = showOverviewArea ? \"block\" : \"none\";\n renderCards(overview.cards || []);\n renderPlugins(plugins.plugins || []);\n };\n\n const loadInitialData = async () => {\n applySetupDesign();\n\n const session = await fetchJson(dashboardConfig.basePath + \"/api/session\");\n if (!session.authenticated) {\n window.location.href = dashboardConfig.basePath + \"/login\";\n return;\n }\n\n state.session = session;\n el.userMeta.textContent = session.user.username + \" • \" + session.guildCount + \" guild(s)\";\n const guilds = await fetchJson(dashboardConfig.basePath + \"/api/guilds\");\n state.guilds = guilds.guilds || [];\n state.selectedGuildId = null;\n renderServerRail();\n updateContextLabel();\n\n el.tabHome.addEventListener(\"click\", () => {\n state.activeMainTab = \"home\";\n applyMainTab();\n });\n\n el.tabPlugins.addEventListener(\"click\", () => {\n state.activeMainTab = \"plugins\";\n applyMainTab();\n });\n\n applyMainTab();\n await refreshContent();\n };\n\n loadInitialData().catch((error) => {\n el.userMeta.textContent = \"Load failed\";\n console.error(error);\n });\n </script>\n</body>\n</html>`;\n}\n","import { renderDashboardHtml } from \"../templates\";\nimport type { DashboardTemplateRenderer } from \"../types\";\n\nfunction escapeHtml(value: string): string {\n return value\n .replaceAll(\"&\", \"&amp;\")\n .replaceAll(\"<\", \"&lt;\")\n .replaceAll(\">\", \"&gt;\")\n .replaceAll('\"', \"&quot;\")\n .replaceAll(\"'\", \"&#039;\");\n}\n\nfunction extractDashboardScript(html: string): string {\n const match = html.match(/<script>([\\s\\S]*)<\\/script>\\s*<\\/body>\\s*<\\/html>\\s*$/);\n if (!match) {\n throw new Error(\"Failed to resolve dashboard script for compact template.\");\n }\n\n return match[1];\n}\n\nconst compactCss = `\n:root {\n color-scheme: dark;\n --bg: #0f1221;\n --rail: #171a2d;\n --content-bg: #0f1426;\n --panel: #1f243b;\n --panel-2: #2a314e;\n --text: #f5f7ff;\n --muted: #aab1d6;\n --primary: #7c87ff;\n --success: #2bd4a6;\n --warning: #ffd166;\n --danger: #ff6f91;\n --info: #66d9ff;\n --border: rgba(255, 255, 255, 0.12);\n}\n* { box-sizing: border-box; }\nbody {\n margin: 0;\n font-family: Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;\n background: radial-gradient(circle at 0% 0%, #1b2140 0%, var(--bg) 45%);\n color: var(--text);\n}\n.shell {\n min-height: 100vh;\n display: grid;\n grid-template-rows: auto 1fr;\n}\n.topbar {\n display: grid;\n grid-template-columns: auto 1fr auto;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n border-bottom: 1px solid var(--border);\n background: rgba(15, 20, 38, 0.7);\n backdrop-filter: blur(8px);\n}\n.brand { font-weight: 800; letter-spacing: .2px; }\n.center-title { text-align: center; font-weight: 700; color: #d8defc; }\n.pill {\n justify-self: end;\n padding: 4px 8px;\n border-radius: 999px;\n border: 1px solid var(--border);\n color: var(--muted);\n font-size: .75rem;\n}\n.layout {\n display: grid;\n grid-template-columns: 80px 1fr;\n min-height: 0;\n}\n.sidebar {\n border-right: 1px solid var(--border);\n background: linear-gradient(180deg, var(--rail), #121528);\n padding: 10px 0;\n}\n.server-rail {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 10px;\n}\n.server-item {\n position: relative;\n width: 46px;\n height: 46px;\n border: none;\n border-radius: 14px;\n overflow: visible;\n background: var(--panel);\n color: #fff;\n font-weight: 700;\n cursor: pointer;\n transition: transform .15s ease, background .15s ease;\n}\n.server-item:hover { transform: translateY(-1px); background: #323b5f; }\n.server-item.active { background: var(--primary); transform: translateY(-2px); }\n.server-item-indicator {\n position: absolute;\n left: -8px;\n top: 50%;\n transform: translateY(-50%) scaleY(.5);\n width: 3px;\n height: 18px;\n background: #fff;\n border-radius: 999px;\n opacity: 0;\n transition: opacity .15s ease, transform .15s ease;\n}\n.server-item.active .server-item-indicator {\n opacity: 1;\n transform: translateY(-50%) scaleY(1);\n}\n.server-avatar { width: 100%; height: 100%; object-fit: cover; border-radius: inherit; }\n.server-fallback { display: grid; place-items: center; width: 100%; height: 100%; }\n.server-status {\n position: absolute;\n right: -3px;\n bottom: -3px;\n width: 11px;\n height: 11px;\n border-radius: 999px;\n border: 2px solid var(--rail);\n background: #35d489;\n}\n.server-status.offline { background: #7f8bb3; }\n.content {\n min-width: 0;\n padding: 12px;\n}\n.container {\n background: rgba(23, 28, 48, 0.6);\n border: 1px solid var(--border);\n border-radius: 12px;\n padding: 12px;\n}\n.main-tabs { display: flex; gap: 8px; margin-bottom: 10px; }\nbutton {\n border: 1px solid var(--border);\n background: var(--panel-2);\n color: var(--text);\n border-radius: 8px;\n padding: 7px 10px;\n cursor: pointer;\n}\nbutton.primary { background: var(--primary); border: none; }\nbutton.danger { background: #4a2230; border-color: rgba(255,111,145,.45); }\n.main-tab.active, .home-category-btn.active { background: var(--primary); border-color: transparent; }\n.section-title { margin: 12px 0 8px; color: #dce3ff; font-size: .95rem; }\n.grid { display: grid; gap: 10px; }\n.cards { grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }\n.panel {\n background: linear-gradient(180deg, rgba(42,49,78,.7), rgba(31,36,59,.85));\n border: 1px solid var(--border);\n border-radius: 10px;\n padding: 12px;\n}\n.title { color: var(--muted); font-size: .83rem; }\n.value { font-size: 1.25rem; font-weight: 800; margin-top: 5px; }\n.subtitle { margin-top: 5px; color: var(--muted); font-size: .8rem; }\n.home-categories { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }\n.home-sections { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }\n.home-section-panel { flex: 0 0 100%; max-width: 100%; }\n.home-width-50 { flex-basis: calc(50% - 5px); max-width: calc(50% - 5px); }\n.home-width-33 { flex-basis: calc(33.333333% - 6.67px); max-width: calc(33.333333% - 6.67px); }\n.home-width-20 { flex-basis: calc(20% - 8px); max-width: calc(20% - 8px); }\n.home-fields, .plugin-fields { display: grid; gap: 8px; margin-top: 8px; }\n.home-field, .plugin-field { display: grid; gap: 5px; }\n.home-field label, .plugin-field > label { color: var(--muted); font-size: .8rem; }\n.home-input, .home-textarea, .home-select {\n width: 100%;\n border: 1px solid var(--border);\n background: var(--panel-2);\n color: var(--text);\n border-radius: 8px;\n padding: 7px 9px;\n}\n.home-textarea { min-height: 88px; resize: vertical; }\n.home-checkbox { width: 17px; height: 17px; }\n.home-field-row { display: flex; align-items: center; gap: 8px; }\n.home-message { margin-top: 6px; color: var(--muted); font-size: .8rem; }\n.lookup-wrap { position: relative; }\n.lookup-results {\n position: absolute;\n left: 0;\n right: 0;\n top: calc(100% + 5px);\n z-index: 20;\n border: 1px solid var(--border);\n background: #1f2742;\n border-radius: 8px;\n max-height: 220px;\n overflow: auto;\n display: none;\n}\n.lookup-item {\n width: 100%;\n border: none;\n border-radius: 0;\n text-align: left;\n padding: 8px 10px;\n background: transparent;\n}\n.lookup-item:hover { background: #2d3658; }\n.lookup-selected { margin-top: 5px; font-size: .8rem; color: var(--muted); }\n.actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }\n.kv-item {\n display: flex;\n justify-content: space-between;\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 7px 9px;\n background: var(--panel-2);\n}\n.list-editor {\n border: 1px solid var(--border);\n border-radius: 8px;\n background: var(--panel-2);\n padding: 8px;\n display: grid;\n gap: 8px;\n}\n.list-items { display: grid; gap: 6px; }\n.list-item {\n display: grid;\n grid-template-columns: auto 1fr auto;\n gap: 8px;\n align-items: center;\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 6px 8px;\n background: var(--panel);\n}\n.list-item.dragging { opacity: .6; }\n.drag-handle { color: var(--muted); user-select: none; cursor: grab; font-size: .9rem; }\n.list-input { width: 100%; border: none; outline: none; background: transparent; color: var(--text); }\n.list-add { justify-self: start; }\n.empty { color: var(--muted); font-size: .9rem; }\n@media (max-width: 980px) {\n .layout { grid-template-columns: 70px 1fr; }\n .home-width-50, .home-width-33, .home-width-20 { flex-basis: 100%; max-width: 100%; }\n}\n`;\n\nexport const compactDashboardTemplateRenderer: DashboardTemplateRenderer = ({\n dashboardName,\n basePath,\n setupDesign\n}) => {\n const script = extractDashboardScript(renderDashboardHtml(dashboardName, basePath, setupDesign));\n const safeName = escapeHtml(dashboardName);\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>${safeName}</title>\n <style>${compactCss}</style>\n</head>\n<body>\n <div class=\"shell\">\n <header class=\"topbar\">\n <div class=\"brand\">${safeName}</div>\n <div id=\"centerTitle\" class=\"center-title\">User Dashboard</div>\n <div id=\"userMeta\" class=\"pill\">Loading...</div>\n </header>\n\n <div class=\"layout\">\n <aside class=\"sidebar\">\n <div id=\"serverRail\" class=\"server-rail\"></div>\n </aside>\n\n <main class=\"content\">\n <div class=\"container\">\n <div class=\"main-tabs\">\n <button id=\"tabHome\" class=\"main-tab active\">Home</button>\n <button id=\"tabPlugins\" class=\"main-tab\">Plugins</button>\n </div>\n\n <section id=\"homeArea\">\n <div class=\"section-title\">Home</div>\n <section id=\"homeCategories\" class=\"home-categories\"></section>\n <section id=\"homeSections\" class=\"home-sections\"></section>\n\n <section id=\"overviewArea\">\n <div class=\"section-title\">Dashboard Stats</div>\n <section id=\"overviewCards\" class=\"grid cards\"></section>\n </section>\n </section>\n\n <section id=\"pluginsArea\" style=\"display:none;\">\n <div class=\"section-title\">Plugins</div>\n <section id=\"plugins\" class=\"grid\"></section>\n </section>\n </div>\n </main>\n </div>\n </div>\n\n <script>${script}</script>\n</body>\n</html>`;\n};\n","import { renderDashboardHtml } from \"../templates\";\nimport type { DashboardTemplateRenderer } from \"../types\";\n\nexport const defaultDashboardTemplateRenderer: DashboardTemplateRenderer = ({\n dashboardName,\n basePath,\n setupDesign\n}) => renderDashboardHtml(dashboardName, basePath, setupDesign);\n","import type { DashboardTemplateRenderer } from \"../types\";\nimport { compactDashboardTemplateRenderer } from \"./compact\";\nimport { defaultDashboardTemplateRenderer } from \"./default\";\n\nexport const builtinTemplateRenderers: Record<string, DashboardTemplateRenderer> = {\n default: defaultDashboardTemplateRenderer,\n compact: compactDashboardTemplateRenderer\n};\n\nexport function getBuiltinTemplateRenderer(templateId: string): DashboardTemplateRenderer | undefined {\n return builtinTemplateRenderers[templateId];\n}\n","import type {\n DashboardHomeBuilder,\n DashboardContext,\n DashboardOptions,\n DashboardScope,\n DashboardTemplateRenderer,\n HomeActionPayload,\n HomeCategory,\n HomeSection,\n HomeSectionAction,\n HomeSectionField,\n PluginActionResult\n} from \"./types\";\n\ntype HomeActionHandler = (\n context: DashboardContext,\n payload: HomeActionPayload\n) => Promise<PluginActionResult> | PluginActionResult;\n\ntype HomeLoadHandler = (\n context: DashboardContext,\n section: HomeSection\n) => Promise<Partial<HomeSection> | HomeSection | void> | Partial<HomeSection> | HomeSection | void;\n\ninterface DesignerPageDefinition {\n pageId: string;\n category: HomeCategory;\n section: HomeSection;\n}\n\nclass CategoryBuilder {\n private sections: HomeSection[] = [];\n\n constructor(\n private readonly scope: DashboardScope,\n private readonly categoryId: string,\n private readonly categoryLabel: string\n ) {}\n\n section(input: {\n id: string;\n title: string;\n description?: string;\n width?: 100 | 50 | 33 | 20;\n fields?: HomeSectionField[];\n actions?: HomeSectionAction[];\n }): this {\n this.sections.push({\n id: input.id,\n title: input.title,\n description: input.description,\n width: input.width,\n fields: input.fields ?? [],\n actions: input.actions ?? [],\n scope: this.scope,\n categoryId: this.categoryId\n });\n\n return this;\n }\n\n buildCategory(): HomeCategory {\n return {\n id: this.categoryId,\n label: this.categoryLabel,\n scope: this.scope\n };\n }\n\n buildSections(): HomeSection[] {\n return [...this.sections];\n }\n}\n\nexport class DashboardDesigner {\n private readonly partialOptions: Partial<DashboardOptions>;\n private readonly categories: CategoryBuilder[] = [];\n private readonly pages: DesignerPageDefinition[] = [];\n private readonly homeActions: Record<string, HomeActionHandler> = {};\n private readonly loadHandlers: Record<string, HomeLoadHandler> = {};\n private readonly saveHandlers: Record<string, HomeActionHandler> = {};\n\n constructor(baseOptions: Omit<DashboardOptions, \"home\">) {\n this.partialOptions = { ...baseOptions };\n }\n\n setup(input: {\n ownerIds?: string[];\n botInvitePermissions?: string;\n botInviteScopes?: string[];\n dashboardName?: string;\n basePath?: string;\n uiTemplate?: string;\n }): this {\n if (input.ownerIds) this.partialOptions.ownerIds = input.ownerIds;\n if (input.botInvitePermissions) this.partialOptions.botInvitePermissions = input.botInvitePermissions;\n if (input.botInviteScopes) this.partialOptions.botInviteScopes = input.botInviteScopes;\n if (input.dashboardName) this.partialOptions.dashboardName = input.dashboardName;\n if (input.basePath) this.partialOptions.basePath = input.basePath;\n if (input.uiTemplate) this.partialOptions.uiTemplate = input.uiTemplate;\n return this;\n }\n\n useTemplate(templateId: string): this {\n this.partialOptions.uiTemplate = templateId;\n return this;\n }\n\n addTemplate(templateId: string, renderer: DashboardTemplateRenderer): this {\n this.partialOptions.uiTemplates = {\n ...(this.partialOptions.uiTemplates ?? {}),\n [templateId]: renderer\n };\n\n return this;\n }\n\n setupDesign(input: {\n bg?: string;\n rail?: string;\n contentBg?: string;\n panel?: string;\n panel2?: string;\n text?: string;\n muted?: string;\n primary?: string;\n success?: string;\n warning?: string;\n danger?: string;\n info?: string;\n border?: string;\n }): this {\n this.partialOptions.setupDesign = {\n ...(this.partialOptions.setupDesign ?? {}),\n ...input\n };\n\n return this;\n }\n\n userCategory(categoryId: string, categoryLabel: string, build: (builder: CategoryBuilder) => void): this {\n const builder = new CategoryBuilder(\"user\", categoryId, categoryLabel);\n build(builder);\n this.categories.push(builder);\n return this;\n }\n\n guildCategory(categoryId: string, categoryLabel: string, build: (builder: CategoryBuilder) => void): this {\n const builder = new CategoryBuilder(\"guild\", categoryId, categoryLabel);\n build(builder);\n this.categories.push(builder);\n return this;\n }\n\n setupCategory(categoryId: string, categoryLabel: string, build: (builder: CategoryBuilder) => void): this {\n const builder = new CategoryBuilder(\"setup\", categoryId, categoryLabel);\n build(builder);\n this.categories.push(builder);\n return this;\n }\n\n setupPage(input: {\n id: string;\n title: string;\n label?: string;\n scope?: DashboardScope;\n categoryId?: string;\n description?: string;\n width?: 100 | 50 | 33 | 20;\n fields?: HomeSectionField[];\n actions?: HomeSectionAction[];\n }): this {\n const scope = input.scope ?? \"user\";\n const categoryId = input.categoryId ?? input.id;\n\n this.pages.push({\n pageId: input.id,\n category: {\n id: categoryId,\n label: input.label ?? input.title,\n scope\n },\n section: {\n id: input.id,\n title: input.title,\n description: input.description,\n width: input.width,\n scope,\n categoryId,\n fields: input.fields ?? [],\n actions: input.actions ?? []\n }\n });\n\n return this;\n }\n\n onHomeAction(actionId: string, handler: HomeActionHandler): this {\n this.homeActions[actionId] = handler;\n return this;\n }\n\n onLoad(pageId: string, handler: HomeLoadHandler): this {\n this.loadHandlers[pageId] = handler;\n return this;\n }\n\n onload(pageId: string, handler: HomeLoadHandler): this {\n return this.onLoad(pageId, handler);\n }\n\n onSave(pageId: string, handler: HomeActionHandler): this {\n this.saveHandlers[pageId] = handler;\n return this;\n }\n\n onsave(pageId: string, handler: HomeActionHandler): this {\n return this.onSave(pageId, handler);\n }\n\n build(): DashboardOptions {\n const staticCategories = this.categories.map((item) => item.buildCategory());\n const staticSections = this.categories.flatMap((item) => item.buildSections());\n const pageCategories = this.pages.map((item) => item.category);\n const baseSections = [...staticSections, ...this.pages.map((item) => item.section)];\n\n const categoryMap = new Map<string, HomeCategory>();\n for (const category of [...staticCategories, ...pageCategories]) {\n const key = `${category.scope}:${category.id}`;\n if (!categoryMap.has(key)) {\n categoryMap.set(key, category);\n }\n }\n\n const categories = [...categoryMap.values()];\n const saveActionIds: Record<string, string> = {};\n for (const section of baseSections) {\n if (this.saveHandlers[section.id]) {\n saveActionIds[section.id] = `save:${section.id}`;\n }\n }\n\n const resolvedActions: Record<string, HomeActionHandler> = {\n ...this.homeActions\n };\n\n for (const [sectionId, handler] of Object.entries(this.saveHandlers)) {\n resolvedActions[saveActionIds[sectionId]] = handler;\n }\n\n const home: DashboardHomeBuilder = {\n getCategories: () => categories,\n getSections: async (context) => {\n const sections: HomeSection[] = [];\n\n for (const sourceSection of baseSections) {\n let section: HomeSection = {\n ...sourceSection,\n fields: sourceSection.fields ? [...sourceSection.fields] : [],\n actions: sourceSection.actions ? [...sourceSection.actions] : []\n };\n\n const saveActionId = saveActionIds[section.id];\n if (saveActionId && !section.actions?.some((action) => action.id === saveActionId)) {\n section.actions = [...(section.actions ?? []), {\n id: saveActionId,\n label: \"Save\",\n variant: \"primary\"\n }];\n }\n\n const loadHandler = this.loadHandlers[section.id];\n if (loadHandler) {\n const loaded = await loadHandler(context, section);\n if (loaded) {\n section = {\n ...section,\n ...loaded,\n fields: loaded.fields ?? section.fields,\n actions: loaded.actions ?? section.actions\n };\n }\n }\n\n sections.push(section);\n }\n\n return sections;\n },\n actions: resolvedActions as DashboardHomeBuilder[\"actions\"]\n };\n\n return {\n ...(this.partialOptions as DashboardOptions),\n home\n };\n }\n}\n\nexport function createDashboardDesigner(baseOptions: Omit<DashboardOptions, \"home\">): DashboardDesigner {\n return new DashboardDesigner(baseOptions);\n}\n"],"mappings":"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A;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoVf,SAAS,WAAW,OAAuB;AACzC,SAAO,MACJ,WAAW,KAAK,OAAO,EACvB,WAAW,KAAK,MAAM,EACtB,WAAW,KAAK,MAAM,EACtB,WAAW,KAAK,QAAQ,EACxB,WAAW,KAAK,QAAQ;AAC7B;AAEO,SAAS,oBAAoB,MAAc,UAAkB,aAA6C;AAC/G,QAAM,WAAW,WAAW,IAAI;AAChC,QAAM,aAAa,KAAK,UAAU,EAAE,UAAU,aAAa,eAAe,CAAC,EAAE,CAAC;AAE9E,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA,WAKE,QAAQ;AAAA,WACR,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6BAUY,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,8BA+BP,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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","names":["escapeHtml","DISCORD_API"]}
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@developer.krd/discord-dashboard",
3
+ "version": "0.1.0",
4
+ "description": "Plug-and-play Discord bot dashboard with built-in frontend and Discord OAuth2 login.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "dist/index.cjs",
8
+ "module": "dist/index.js",
9
+ "types": "dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "engines": {
21
+ "node": ">=18"
22
+ },
23
+ "scripts": {
24
+ "build": "tsup",
25
+ "dev": "tsup --watch",
26
+ "example": "tsx examples/basic-bot.ts",
27
+ "real-bot": "tsx examples/real-bot/main.ts",
28
+ "typecheck": "tsc --noEmit",
29
+ "prepare": "npm run build"
30
+ },
31
+ "keywords": [
32
+ "discord",
33
+ "dashboard",
34
+ "oauth2",
35
+ "express",
36
+ "bot"
37
+ ],
38
+ "dependencies": {
39
+ "compression": "^1.8.0",
40
+ "express": "^4.21.2",
41
+ "express-session": "^1.18.1",
42
+ "helmet": "^8.0.0"
43
+ },
44
+ "devDependencies": {
45
+ "@types/compression": "^1.7.5",
46
+ "@types/express": "^4.17.22",
47
+ "@types/express-session": "^1.18.0",
48
+ "@types/node": "^22.13.10",
49
+ "discord.js": "^14.25.1",
50
+ "dotenv": "^16.4.7",
51
+ "tsup": "^8.4.0",
52
+ "tsx": "^4.20.3",
53
+ "typescript": "^5.8.2"
54
+ }
55
+ }