feat(ui): implement Catppuccin Macchiato color theory and aesthetics
husteno-o committed 1 months ago
117b4fd
1 parent c8eca38
commit 117b4fd
6 files changed
+226 -120
Showing 6 changed files+226-120
▼apps/bot/src/commands/activity.ts
+44-11
| @@ -2,9 +2,19 @@ import { db, users } from "@devpulse/database"; | ||
2 | 2 | import { githubClient } from "@devpulse/github"; |
3 | 3 | import { SlashCommandBuilder } from "discord.js"; |
4 | 4 | import { eq } from "drizzle-orm"; |
5 | -import { BrandColors } from "../ui/colors.js"; | |
5 | +import { BrandColors, Macchiato } from "../ui/colors.js"; | |
6 | 6 | import { createBaseEmbed, createErrorEmbed } from "../ui/embeds.js"; |
7 | 7 | import { NF } from "../ui/icons.js"; |
8 | +import { | |
9 | + ANSI, | |
10 | + renderTuiCard, | |
11 | + tuiBottomBar, | |
12 | + tuiDivider, | |
13 | + tuiLine, | |
14 | + tuiPrompt, | |
15 | + tuiRow2, | |
16 | + tuiTopBar, | |
17 | +} from "../ui/tui.js"; | |
8 | 18 | import type { Command } from "./types.js"; |
9 | 19 | |
10 | 20 | export const activityCommand: Command = { |
| @@ -46,20 +56,43 @@ export const activityCommand: Command = { | ||
46 | 56 | const data = await githubClient.getDeveloperActivity(username); |
47 | 57 | |
48 | 58 | const maxCount = Math.max(...data.weekdayDistribution.map((d) => d.count), 1); |
49 | - const histogram = data.weekdayDistribution | |
50 | - .map((d) => { | |
51 | - const barLen = Math.round((d.count / maxCount) * 12); | |
52 | - const bar = "█".repeat(barLen).padEnd(12, "░"); | |
53 | - return `${d.day} ${bar} ${d.count}`; | |
54 | - }) | |
55 | - .join("\n"); | |
59 | + const histLines = data.weekdayDistribution.map((d) => { | |
60 | + const barLen = Math.round((d.count / maxCount) * 12); | |
61 | + const bar = "■".repeat(barLen).padEnd(12, "□"); | |
62 | + return tuiLine( | |
63 | + `${ANSI.dim}${d.day.padEnd(4)}${ANSI.reset} ${ANSI.cyan}${bar}${ANSI.reset} ${ANSI.yellow}${d.count}${ANSI.reset}`, | |
64 | + ); | |
65 | + }); | |
66 | + | |
67 | + const tui = renderTuiCard([ | |
68 | + tuiTopBar("DEVELOPER TELEMETRY"), | |
69 | + tuiPrompt(`gitbot activity @${data.user.login}`), | |
70 | + tuiDivider("30-DAY METRICS"), | |
71 | + tuiRow2( | |
72 | + "Commits", | |
73 | + `${ANSI.cyan}${data.commitsCount}${ANSI.reset}`, | |
74 | + "Reviews", | |
75 | + `${ANSI.yellow}${data.reviewsCount}${ANSI.reset}`, | |
76 | + ), | |
77 | + tuiRow2( | |
78 | + "PRs Open", | |
79 | + `${ANSI.green}${data.prsOpened}${ANSI.reset}`, | |
80 | + "Merged", | |
81 | + `${ANSI.magenta}${data.prsMerged}${ANSI.reset}`, | |
82 | + ), | |
83 | + tuiRow2("Resolved", `${ANSI.green}${data.issuesResolved}${ANSI.reset}`), | |
84 | + tuiDivider("WEEKDAY CADENCE"), | |
85 | + ...histLines, | |
86 | + tuiBottomBar(), | |
87 | + ]); | |
88 | + | |
89 | + const badges = `👥 **Developer:** \`@${data.user.login}\` • 💻 **${data.commitsCount}** Commits • 🔀 **${data.prsMerged}** PRs Merged\n\n`; | |
56 | 90 | |
57 | 91 | const embed = createBaseEmbed(`${NF.speedometer} Developer Activity: @${data.user.login}`) |
58 | 92 | .setThumbnail(data.user.avatarUrl) |
93 | + .setColor(Macchiato.mauve) | |
59 | 94 | .setURL(data.user.htmlUrl) |
60 | - .setDescription( | |
61 | - `**LAST 30 DAYS TELEMETRY**\n\`\`\`text\nCommits: ${data.commitsCount.toString().padEnd(6)}\nPRs Opened: ${data.prsOpened.toString().padEnd(6)}\nPRs Merged: ${data.prsMerged.toString().padEnd(6)}\nReviews: ${data.reviewsCount.toString().padEnd(6)}\nIssues Resolved: ${data.issuesResolved.toString().padEnd(6)}\n\`\`\`\n**ACTIVITY DISTRIBUTION**\n\`\`\`text\n${histogram}\n\`\`\``, | |
62 | - ); | |
95 | + .setDescription(`${badges}${tui}`); | |
63 | 96 | |
64 | 97 | await interaction.editReply({ embeds: [embed] }); |
65 | 98 | return; |
66 | 99 | |
▼apps/bot/src/commands/help.ts
+43-44
| @@ -120,56 +120,55 @@ export function getModuleHelpEmbed(moduleName: string) { | ||
120 | 120 | .setDescription(`${header}${tui}`); |
121 | 121 | } |
122 | 122 | |
123 | +export function createHelpDashboardEmbed() { | |
124 | + const tui = renderTuiCard([ | |
125 | + tuiTopBar("GITBOT CORE ENGINE v2.4"), | |
126 | + tuiPrompt("gitbot --help"), | |
127 | + tuiDivider("ACTIVE SUBSYSTEMS"), | |
128 | + tuiLine( | |
129 | + `${ANSI.yellow}[1] REPO ${ANSI.reset}: ${ANSI.white}/repo <target> [view]${ANSI.reset}`, | |
130 | + ), | |
131 | + tuiLine( | |
132 | + `${ANSI.yellow}[2] PR ${ANSI.reset}: ${ANSI.white}/pr <view|list|review>${ANSI.reset}`, | |
133 | + ), | |
134 | + tuiLine( | |
135 | + `${ANSI.yellow}[3] SEARCH ${ANSI.reset}: ${ANSI.white}/search <code|issues|prs>${ANSI.reset}`, | |
136 | + ), | |
137 | + tuiLine( | |
138 | + `${ANSI.yellow}[4] CODE ${ANSI.reset}: ${ANSI.white}/code view, /code blame${ANSI.reset}`, | |
139 | + ), | |
140 | + tuiLine(`${ANSI.yellow}[5] TRACE ${ANSI.reset}: ${ANSI.white}/why, /investigate${ANSI.reset}`), | |
141 | + tuiLine(`${ANSI.yellow}[6] STATS ${ANSI.reset}: ${ANSI.white}/activity, /team${ANSI.reset}`), | |
142 | + tuiLine(`${ANSI.yellow}[7] RADAR ${ANSI.reset}: ${ANSI.white}/trending, /watch${ANSI.reset}`), | |
143 | + tuiLine( | |
144 | + `${ANSI.yellow}[8] CI/CD ${ANSI.reset}: ${ANSI.white}/actions status, /release${ANSI.reset}`, | |
145 | + ), | |
146 | + tuiLine( | |
147 | + `${ANSI.yellow}[9] SECURE ${ANSI.reset}: ${ANSI.white}/security audit, /connect${ANSI.reset}`, | |
148 | + ), | |
149 | + tuiDivider("COMMAND CENTER"), | |
150 | + tuiLine(`${ANSI.green}▸ Interactive Developer Console for Discord${ANSI.reset}`), | |
151 | + tuiLine(`${ANSI.cyan}▸ Pick a manual below or type a command${ANSI.reset}`), | |
152 | + tuiBottomBar(), | |
153 | + ]); | |
154 | + | |
155 | + const header = | |
156 | + `### 🐙 [**GITBOT — GitHub Developer Operating System**](https://github.com/husteno-o/git-discord-bot)\n` + | |
157 | + `> *The ultimate Discord command center engineered so developers never have to leave chat for GitHub workflows.*\n\n` + | |
158 | + `🌸 **Catppuccin Macchiato** • ⚡ **Instant Telemetry** • 🌿 **PR Power Tools** • 🛡️ **Zero-Config Security**\n\n`; | |
159 | + | |
160 | + return createBaseEmbed(`${NF.github} GITBOT Developer Center`) | |
161 | + .setColor(BrandColors.primary) | |
162 | + .setDescription(`${header}${tui}`); | |
163 | +} | |
164 | + | |
123 | 165 | export const helpCommand: Command = { |
124 | 166 | data: new SlashCommandBuilder() |
125 | 167 | .setName("help") |
126 | 168 | .setDescription("Explore GITBOT GitHub Power-User feature sets and command suites"), |
127 | 169 | |
128 | 170 | async execute(interaction) { |
129 | - const tui = renderTuiCard([ | |
130 | - tuiTopBar("GITBOT CORE ENGINE v2.4"), | |
131 | - tuiPrompt("gitbot --help"), | |
132 | - tuiDivider("ACTIVE SUBSYSTEMS"), | |
133 | - tuiLine( | |
134 | - `${ANSI.yellow}[1] REPO ${ANSI.reset}: ${ANSI.white}/repo <target> [view]${ANSI.reset}`, | |
135 | - ), | |
136 | - tuiLine( | |
137 | - `${ANSI.yellow}[2] PR ${ANSI.reset}: ${ANSI.white}/pr <view|list|review>${ANSI.reset}`, | |
138 | - ), | |
139 | - tuiLine( | |
140 | - `${ANSI.yellow}[3] SEARCH ${ANSI.reset}: ${ANSI.white}/search <code|issues|prs>${ANSI.reset}`, | |
141 | - ), | |
142 | - tuiLine( | |
143 | - `${ANSI.yellow}[4] CODE ${ANSI.reset}: ${ANSI.white}/code view, /code blame${ANSI.reset}`, | |
144 | - ), | |
145 | - tuiLine( | |
146 | - `${ANSI.yellow}[5] TRACE ${ANSI.reset}: ${ANSI.white}/why, /investigate${ANSI.reset}`, | |
147 | - ), | |
148 | - tuiLine(`${ANSI.yellow}[6] STATS ${ANSI.reset}: ${ANSI.white}/activity, /team${ANSI.reset}`), | |
149 | - tuiLine( | |
150 | - `${ANSI.yellow}[7] RADAR ${ANSI.reset}: ${ANSI.white}/trending, /watch${ANSI.reset}`, | |
151 | - ), | |
152 | - tuiLine( | |
153 | - `${ANSI.yellow}[8] CI/CD ${ANSI.reset}: ${ANSI.white}/actions status, /release${ANSI.reset}`, | |
154 | - ), | |
155 | - tuiLine( | |
156 | - `${ANSI.yellow}[9] SECURE ${ANSI.reset}: ${ANSI.white}/security audit, /connect${ANSI.reset}`, | |
157 | - ), | |
158 | - tuiDivider("COMMAND CENTER"), | |
159 | - tuiLine(`${ANSI.green}▸ Interactive Developer Console for Discord${ANSI.reset}`), | |
160 | - tuiLine(`${ANSI.cyan}▸ Pick a manual below or type a command${ANSI.reset}`), | |
161 | - tuiBottomBar(), | |
162 | - ]); | |
163 | - | |
164 | - const header = | |
165 | - `### 🐙 [**GITBOT — GitHub Developer Operating System**](https://github.com/husteno-o/git-discord-bot)\n` + | |
166 | - `> *The ultimate Discord command center engineered so developers never have to leave chat for GitHub workflows.*\n\n` + | |
167 | - `🚀 **Instant Telemetry** • 🌿 **PR Power Tools** • 🛡️ **Zero-Config Security**\n\n`; | |
168 | - | |
169 | - const embed = createBaseEmbed(`${NF.github} GITBOT Developer Center`) | |
170 | - .setColor(BrandColors.primary) | |
171 | - .setDescription(`${header}${tui}`); | |
172 | - | |
171 | + const embed = createHelpDashboardEmbed(); | |
173 | 172 | const components = [createHelpSelect()]; |
174 | 173 | await interaction.reply({ embeds: [embed], components }); |
175 | 174 | }, |
176 | 175 | |
▼apps/bot/src/events/messageCreate.ts
+2-7
| @@ -4,14 +4,13 @@ import { logger } from "@devpulse/logger"; | ||
4 | 4 | import { scanForSecrets } from "@devpulse/security"; |
5 | 5 | import type { Message } from "discord.js"; |
6 | 6 | import { eq } from "drizzle-orm"; |
7 | -import { BrandColors } from "../ui/colors.js"; | |
7 | +import { createHelpDashboardEmbed } from "../commands/help.js"; | |
8 | 8 | import { |
9 | 9 | createDeleteSecretButton, |
10 | 10 | createHelpSelect, |
11 | 11 | createRepoNavButtons, |
12 | 12 | } from "../ui/components.js"; |
13 | 13 | import { |
14 | - createBaseEmbed, | |
15 | 14 | createErrorEmbed, |
16 | 15 | createRepoDashboardEmbed, |
17 | 16 | createSecurityAlertEmbed, |
| @@ -68,11 +67,7 @@ export async function handleMessageCreate(message: Message): Promise<void> { | ||
68 | 67 | |
69 | 68 | // 2. Text Command: /help or !help |
70 | 69 | if (content === "/help" || content === "!help" || content === "help") { |
71 | - const embed = createBaseEmbed(`${NF.github} GITBOT — Developer Operating System`) | |
72 | - .setColor(BrandColors.primary) | |
73 | - .setDescription( | |
74 | - `Welcome to **GITBOT**, your terminal command center for GitHub inside Discord.\n\n**Quick Commands:**\n• \`/repo <owner/repo>\` ${NF.arrowRight} Telemetry, health & velocity (e.g. \`/repo swadhinbiswas/warren\`)\n• \`/pr <repo> <number>\` ${NF.arrowRight} Pull request reviews, diffs & 1-click merge\n• \`/actions <repo>\` ${NF.arrowRight} CI/CD workflow tree & run status\n• \`/trending\` ${NF.arrowRight} Rising GitHub repositories across languages\n• \`/tools\` ${NF.arrowRight} Developer utility console (Base64, JWT, Hash, Cron)\n\nUse the menu below to explore all feature areas:`, | |
75 | - ); | |
70 | + const embed = createHelpDashboardEmbed(); | |
76 | 71 | const components = [createHelpSelect()]; |
77 | 72 | await message.reply({ embeds: [embed], components }); |
78 | 73 | return; |
79 | 74 | |
▼apps/bot/src/ui/colors.ts
+49-9
| @@ -1,11 +1,51 @@ | ||
1 | +// Catppuccin Macchiato Color Palette for GITBOT | |
2 | +// A soothing, high-contrast pastel dark theme crafted with color harmony. | |
3 | + | |
4 | +export const Macchiato = { | |
5 | + rosewater: 0xf4dbd6, | |
6 | + flamingo: 0xf0c6c6, | |
7 | + pink: 0xf5bde6, | |
8 | + mauve: 0xc6a0f6, | |
9 | + red: 0xed8796, | |
10 | + maroon: 0xee99a0, | |
11 | + peach: 0xf5a97f, | |
12 | + yellow: 0xeed49f, | |
13 | + green: 0xa6da95, | |
14 | + teal: 0x8bd5ca, | |
15 | + sky: 0x91d7e3, | |
16 | + sapphire: 0x7dc4e4, | |
17 | + blue: 0x8aadf4, | |
18 | + lavender: 0xb7bdf8, | |
19 | + text: 0xcad3f5, | |
20 | + subtext1: 0xb8c0e0, | |
21 | + subtext0: 0xa5adcb, | |
22 | + overlay2: 0x939ab7, | |
23 | + overlay1: 0x8087a2, | |
24 | + overlay0: 0x6e738d, | |
25 | + surface2: 0x5b6078, | |
26 | + surface1: 0x494d64, | |
27 | + surface0: 0x363a4f, | |
28 | + base: 0x24273a, | |
29 | + mantle: 0x1e2030, | |
30 | + crust: 0x181926, | |
31 | +} as const; | |
32 | + | |
1 | 33 | export const BrandColors = { |
2 | - primary: 0x58a6ff, // GitHub Accent Blue | |
3 | - success: 0x238636, // GitHub Emerald Green | |
4 | - danger: 0xda3633, // GitHub Coral Red | |
5 | - warning: 0xd29922, // GitHub Amber | |
6 | - dark: 0x0d1117, // GitHub Dark Canvas | |
7 | - secondary: 0x30363d, // GitHub Dark Border | |
8 | - github: 0x161b22, // GitHub Card Inset | |
9 | - info: 0x58a6ff, // GitHub Sky | |
10 | - purple: 0xa371f7, // GitHub Merge Purple | |
34 | + primary: Macchiato.mauve, // 0xc6a0f6 - Signature Catppuccin Mauve | |
35 | + secondary: Macchiato.surface1, // 0x494d64 - Muted Surface | |
36 | + success: Macchiato.green, // 0xa6da95 - Macchiato Green | |
37 | + danger: Macchiato.red, // 0xed8796 - Macchiato Red | |
38 | + warning: Macchiato.peach, // 0xf5a97f - Macchiato Peach | |
39 | + info: Macchiato.sapphire, // 0x7dc4e4 - Macchiato Sapphire | |
40 | + purple: Macchiato.mauve, // 0xc6a0f6 - Macchiato Mauve | |
41 | + blue: Macchiato.blue, // 0x8aadf4 - Macchiato Blue | |
42 | + teal: Macchiato.teal, // 0x8bd5ca - Macchiato Teal | |
43 | + lavender: Macchiato.lavender, // 0xb7bdf8 - Macchiato Lavender | |
44 | + yellow: Macchiato.yellow, // 0xeed49f - Macchiato Yellow | |
45 | + rosewater: Macchiato.rosewater, // 0xf4dbd6 - Macchiato Rosewater | |
46 | + pink: Macchiato.pink, // 0xf5bde6 - Macchiato Pink | |
47 | + maroon: Macchiato.maroon, // 0xee99a0 - Macchiato Maroon | |
48 | + sky: Macchiato.sky, // 0x91d7e3 - Macchiato Sky | |
49 | + github: Macchiato.base, // 0x24273a - Macchiato Base | |
50 | + dark: Macchiato.crust, // 0x181926 - Macchiato Crust | |
11 | 51 | }; |
12 | 52 | |
▼apps/bot/src/ui/embeds.ts
+62-38
| @@ -18,7 +18,7 @@ import type { | ||
18 | 18 | } from "@devpulse/github"; |
19 | 19 | import type { DetectedSecret } from "@devpulse/security"; |
20 | 20 | import { EmbedBuilder } from "discord.js"; |
21 | -import { BrandColors } from "./colors.js"; | |
21 | +import { Macchiato } from "./colors.js"; | |
22 | 22 | import { NF } from "./icons.js"; |
23 | 23 | import { |
24 | 24 | ANSI, |
| @@ -36,10 +36,10 @@ import { | ||
36 | 36 | |
37 | 37 | export function createBaseEmbed(title: string, description?: string): EmbedBuilder { |
38 | 38 | const embed = new EmbedBuilder() |
39 | - .setColor(BrandColors.primary) | |
39 | + .setColor(Macchiato.mauve) | |
40 | 40 | .setTitle(title) |
41 | 41 | .setFooter({ |
42 | - text: "GITBOT v2.4.0 • Interactive Terminal Console", | |
42 | + text: "GITBOT v2.4.0 • Catppuccin Macchiato TUI", | |
43 | 43 | iconURL: "https://github.githubassets.com/favicons/favicon.png", |
44 | 44 | }) |
45 | 45 | .setTimestamp(); |
| @@ -64,10 +64,10 @@ export function createErrorEmbed(error: Error | string): EmbedBuilder { | ||
64 | 64 | ]); |
65 | 65 | |
66 | 66 | return new EmbedBuilder() |
67 | - .setColor(BrandColors.danger) | |
67 | + .setColor(Macchiato.red) | |
68 | 68 | .setTitle(`${NF.cross} Execution Error`) |
69 | 69 | .setDescription(tui) |
70 | - .setFooter({ text: "GITBOT Error Console" }) | |
70 | + .setFooter({ text: "GITBOT Error Console • Catppuccin Macchiato" }) | |
71 | 71 | .setTimestamp(); |
72 | 72 | } |
73 | 73 | |
| @@ -130,16 +130,19 @@ export function createRepoDashboardEmbed( | ||
130 | 130 | ), |
131 | 131 | tuiDivider("HEALTH INDEX"), |
132 | 132 | tuiLine( |
133 | - `${ANSI.dim}Health :${ANSI.reset} ${renderMeter(healthScore)} ${ANSI.green}${healthScore}/100${ANSI.reset}`, | |
133 | + `${ANSI.dim}Health :${ANSI.reset} ${renderMeter(healthScore)} ${healthScore >= 80 ? ANSI.green : ANSI.yellow}${healthScore}/100${ANSI.reset}`, | |
134 | 134 | ), |
135 | 135 | tuiBottomBar(), |
136 | 136 | ]); |
137 | 137 | |
138 | 138 | const descQuote = repo.description ? `> *${repo.description.replace(/\n/g, " ")}*\n\n` : ""; |
139 | - const badges = `⭐ **${repo.stars.toLocaleString()}** Stars • 🍴 **${repo.forks.toLocaleString()}** Forks • ${repo.isPrivate ? "🔴 **Private**" : "🟢 **Public**"} • 🏷️ **${repo.license?.spdxId || "No License"}**\n\n`; | |
139 | + const badges = `⭐ **${repo.stars.toLocaleString()}** Stars • 🍴 **${repo.forks.toLocaleString()}** Forks • ${repo.isPrivate ? "🔒 **Private**" : "🌐 **Public**"} • 📜 \`${repo.license?.spdxId || "No License"}\`\n\n`; | |
140 | + | |
141 | + const embedColor = | |
142 | + healthScore >= 80 ? Macchiato.green : healthScore >= 50 ? Macchiato.peach : Macchiato.red; | |
140 | 143 | |
141 | 144 | return new EmbedBuilder() |
142 | - .setColor(healthScore >= 80 ? BrandColors.success : BrandColors.warning) | |
145 | + .setColor(embedColor) | |
143 | 146 | .setTitle(`${NF.github} ${repo.fullName}`) |
144 | 147 | .setAuthor({ |
145 | 148 | name: `GITBOT Terminal • ${repo.fullName}`, |
| @@ -149,7 +152,7 @@ export function createRepoDashboardEmbed( | ||
149 | 152 | .setURL(repo.htmlUrl) |
150 | 153 | .setDescription(`${descQuote}${badges}${tui}`) |
151 | 154 | .setFooter({ |
152 | - text: "GITBOT v2.4.0 • Interactive Terminal Console • Press buttons below to navigate", | |
155 | + text: "GITBOT v2.4.0 • Catppuccin Macchiato TUI • Press buttons below to navigate", | |
153 | 156 | iconURL: "https://github.githubassets.com/favicons/favicon.png", |
154 | 157 | }) |
155 | 158 | .setTimestamp(); |
| @@ -157,7 +160,7 @@ export function createRepoDashboardEmbed( | ||
157 | 160 | |
158 | 161 | export function createRepoHealthEmbed(repo: GitHubRepo, health: RepoHealthScore): EmbedBuilder { |
159 | 162 | const tui = renderTuiCard([ |
160 | - tuiTopBar(`HEALTH SCORE: ${health.overallScore}/100`), | |
163 | + tuiTopBar(`HEALTH: ${health.overallScore}/100`), | |
161 | 164 | tuiPrompt(`gitbot repo ${repo.fullName} --health`), |
162 | 165 | tuiDivider("METRIC GAUGES"), |
163 | 166 | tuiLine( |
| @@ -193,7 +196,14 @@ export function createRepoHealthEmbed(repo: GitHubRepo, health: RepoHealthScore) | ||
193 | 196 | tuiBottomBar(), |
194 | 197 | ]); |
195 | 198 | |
196 | - const badges = `❤️ **Score:** \`${health.overallScore}/100\` • ${health.overallScore >= 80 ? "🟢 **Healthy**" : "🟡 **Needs Maintenance**"} • 📜 **${health.details.commits30d}** Commits (30d)\n\n`; | |
199 | + const badges = `❤️ **Health Score:** \`${health.overallScore}/100\` • ${health.overallScore >= 80 ? "🟢 **Healthy**" : "🟡 **Needs Maintenance**"} • 📜 **${health.details.commits30d}** Commits (30d)\n\n`; | |
200 | + | |
201 | + const embedColor = | |
202 | + health.overallScore >= 80 | |
203 | + ? Macchiato.green | |
204 | + : health.overallScore >= 50 | |
205 | + ? Macchiato.peach | |
206 | + : Macchiato.red; | |
197 | 207 | |
198 | 208 | return createBaseEmbed(`${NF.heart} Health Score: ${repo.fullName}`) |
199 | 209 | .setAuthor({ |
| @@ -202,7 +212,7 @@ export function createRepoHealthEmbed(repo: GitHubRepo, health: RepoHealthScore) | ||
202 | 212 | url: repo.htmlUrl, |
203 | 213 | }) |
204 | 214 | .setURL(repo.htmlUrl) |
205 | - .setColor(health.overallScore > 80 ? BrandColors.success : BrandColors.warning) | |
215 | + .setColor(embedColor) | |
206 | 216 | .setDescription(`${badges}${tui}`); |
207 | 217 | } |
208 | 218 | |
| @@ -234,7 +244,7 @@ export function createRepoGrowthEmbed(repo: GitHubRepo, growth: RepoGrowthMetric | ||
234 | 244 | iconURL: repo.owner.avatarUrl, |
235 | 245 | url: repo.htmlUrl, |
236 | 246 | }) |
237 | - .setColor(BrandColors.primary) | |
247 | + .setColor(Macchiato.lavender) | |
238 | 248 | .setURL(repo.htmlUrl) |
239 | 249 | .setDescription(`${badges}${tui}`); |
240 | 250 | } |
| @@ -266,7 +276,7 @@ export function createDependenciesEmbed(repo: GitHubRepo, deps: RepoDependencies | ||
266 | 276 | tuiBottomBar(), |
267 | 277 | ]); |
268 | 278 | |
269 | - const badges = `📦 **Packages:** \`${deps.totalCount}\` • ${deps.outdatedCount > 0 ? `⚠️ **${deps.outdatedCount}** Outdated` : "🟢 **All Up to Date**"} • 📄 **${deps.manifestFile}**\n\n`; | |
279 | + const badges = `📦 **Packages:** \`${deps.totalCount}\` • ${deps.outdatedCount > 0 ? `⚠️ **${deps.outdatedCount}** Outdated` : "🟢 **All Up to Date**"} • 📄 \`${deps.manifestFile}\`\n\n`; | |
270 | 280 | |
271 | 281 | return createBaseEmbed(`${NF.network} Dependencies: ${repo.fullName}`) |
272 | 282 | .setAuthor({ |
| @@ -274,6 +284,7 @@ export function createDependenciesEmbed(repo: GitHubRepo, deps: RepoDependencies | ||
274 | 284 | iconURL: repo.owner.avatarUrl, |
275 | 285 | url: repo.htmlUrl, |
276 | 286 | }) |
287 | + .setColor(deps.outdatedCount > 0 ? Macchiato.peach : Macchiato.teal) | |
277 | 288 | .setURL(repo.htmlUrl) |
278 | 289 | .setDescription(`${badges}${tui}`); |
279 | 290 | } |
| @@ -325,11 +336,19 @@ export function createDetailedPrEmbed( | ||
325 | 336 | tuiBottomBar(), |
326 | 337 | ]); |
327 | 338 | |
328 | - const badges = `${isReady ? "🟢 **Ready to Merge**" : pr.draft ? "⚪ **Draft**" : "🟡 **In Review**"} • 🌿 \`${pr.headBranch}\` ➔ \`${pr.baseBranch}\` • 👤 **@${pr.author.login}**\n\n`; | |
339 | + const badges = `${isReady ? "🟢 **Ready to Merge**" : pr.draft ? "⚪ **Draft**" : !pr.mergeable ? "🔴 **Conflicts**" : "🟡 **In Review**"} • 🌿 \`${pr.headBranch}\` ➔ \`${pr.baseBranch}\` • 👤 **@${pr.author.login}**\n\n`; | |
340 | + | |
341 | + const prColor = pr.draft | |
342 | + ? Macchiato.overlay1 | |
343 | + : !pr.mergeable | |
344 | + ? Macchiato.red | |
345 | + : isReady | |
346 | + ? Macchiato.green | |
347 | + : Macchiato.peach; | |
329 | 348 | |
330 | 349 | const embed = createBaseEmbed(`${NF.gitPullRequest} PR #${pr.number}: ${pr.title}`) |
331 | 350 | .setURL(pr.htmlUrl) |
332 | - .setColor(isReady ? BrandColors.success : BrandColors.warning) | |
351 | + .setColor(prColor) | |
333 | 352 | .setDescription(`${badges}${tui}`); |
334 | 353 | |
335 | 354 | const warnings: string[] = []; |
| @@ -366,9 +385,9 @@ export function createInvestigationEmbed(investigation: InvestigationTimeline): | ||
366 | 385 | .join("\n"); |
367 | 386 | |
368 | 387 | return createBaseEmbed(`${NF.search} ${investigation.title}`) |
369 | - .setColor(BrandColors.primary) | |
388 | + .setColor(Macchiato.sapphire) | |
370 | 389 | .setDescription( |
371 | - `**Target:** \`${investigation.identifier}\`\n**Summary:** ${investigation.summary}\n\n**Lifecycle Traceability Timeline:**\n\n${steps}`, | |
390 | + `🔍 **Target:** \`${investigation.identifier}\`\n> *${investigation.summary}*\n\n**Lifecycle Traceability Timeline:**\n\n${steps}`, | |
372 | 391 | ); |
373 | 392 | } |
374 | 393 | |
| @@ -377,6 +396,7 @@ export function createCodeViewEmbed(repo: string, file: GitHubFileContent): Embe | ||
377 | 396 | const snippet = file.content.split("\n").slice(0, 15).join("\n"); |
378 | 397 | const embed = createBaseEmbed(`${NF.terminal} ${repo}: ${file.path}`) |
379 | 398 | .setURL(file.htmlUrl) |
399 | + .setColor(Macchiato.blue) | |
380 | 400 | .setDescription( |
381 | 401 | `**Size:** \`${formatBytes(file.size)}\` | **SHA:** \`${file.sha.slice(0, 7)}\`\n\`\`\`${file.path.split(".").pop() || ""}\n${snippet}\n\`\`\``, |
382 | 402 | ); |
| @@ -393,15 +413,15 @@ export function createCodeViewEmbed(repo: string, file: GitHubFileContent): Embe | ||
393 | 413 | } |
394 | 414 | |
395 | 415 | export function createBlameEmbed(repo: string, path: string, blame: GitHubBlameLine): EmbedBuilder { |
396 | - return createBaseEmbed( | |
397 | - `${NF.gitCommit} Blame: ${repo}/${path} (Line ${blame.lineNumber})`, | |
398 | - ).setDescription( | |
399 | - `\`\`\`text\n${blame.code}\n\`\`\`\n` + | |
400 | - `**Last Changed:** ${blame.relatedPrNumber ? `**PR #${blame.relatedPrNumber}**` : `Commit \`${blame.commitSha}\``}\n` + | |
401 | - `**Author:** @${blame.commitAuthor}\n` + | |
402 | - `**Commit:** \`${blame.commitSha}\`\n` + | |
403 | - `**Reason:** "${blame.commitMessage}"`, | |
404 | - ); | |
416 | + return createBaseEmbed(`${NF.gitCommit} Blame: ${repo}/${path} (Line ${blame.lineNumber})`) | |
417 | + .setColor(Macchiato.mauve) | |
418 | + .setDescription( | |
419 | + `\`\`\`text\n${blame.code}\n\`\`\`\n` + | |
420 | + `**Last Changed:** ${blame.relatedPrNumber ? `**PR #${blame.relatedPrNumber}**` : `Commit \`${blame.commitSha}\``}\n` + | |
421 | + `**Author:** @${blame.commitAuthor}\n` + | |
422 | + `**Commit:** \`${blame.commitSha}\`\n` + | |
423 | + `**Reason:** "${blame.commitMessage}"`, | |
424 | + ); | |
405 | 425 | } |
406 | 426 | |
407 | 427 | // 5. ACTIONS TREE EMBED |
| @@ -414,9 +434,11 @@ export function createActionsTreeEmbed(repo: string, runs: any[]): EmbedBuilder | ||
414 | 434 | return `${prefix} ${padAnsi(clipAnsi(r.name, 16), 16)} ${statusIcon} (${r.headBranch})`; |
415 | 435 | }); |
416 | 436 | |
417 | - return createBaseEmbed(`${NF.robot} GitHub Actions: ${repo}`).setDescription( | |
418 | - `Workflow runs on default branch:\n\`\`\`text\nmain\n${lines.join("\n") || "└── No workflow runs found"}\n\`\`\``, | |
419 | - ); | |
437 | + return createBaseEmbed(`${NF.robot} GitHub Actions: ${repo}`) | |
438 | + .setColor(Macchiato.teal) | |
439 | + .setDescription( | |
440 | + `Workflow runs on default branch:\n\`\`\`text\nmain\n${lines.join("\n") || "└── No workflow runs found"}\n\`\`\``, | |
441 | + ); | |
420 | 442 | } |
421 | 443 | |
422 | 444 | // 6. SECURITY EMBED |
| @@ -439,18 +461,16 @@ export function createSecurityAuditEmbed( | ||
439 | 461 | .join("\n\n"); |
440 | 462 | |
441 | 463 | return createBaseEmbed(`${NF.shield} GitHub Security Center: ${repo}`) |
442 | - .setColor( | |
443 | - critical > 0 ? BrandColors.danger : high > 0 ? BrandColors.warning : BrandColors.success, | |
444 | - ) | |
464 | + .setColor(critical > 0 ? Macchiato.red : high > 0 ? Macchiato.peach : Macchiato.green) | |
445 | 465 | .setDescription( |
446 | - `**Advisories Breakdown:**\nCritical: \`${critical}\` | High: \`${high}\` | Medium: \`${medium}\` | Low: \`${low}\`\n\n${topItems || `${NF.check} Zero active vulnerabilities detected!`}`, | |
466 | + `🛡️ **Advisories Breakdown:**\nCritical: \`${critical}\` | High: \`${high}\` | Medium: \`${medium}\` | Low: \`${low}\`\n\n${topItems || `${NF.check} Zero active vulnerabilities detected!`}`, | |
447 | 467 | ); |
448 | 468 | } |
449 | 469 | |
450 | 470 | // 7. RELEASE NOTES EMBED |
451 | 471 | export function createReleaseNotesEmbed(repo: string, notes: any): EmbedBuilder { |
452 | 472 | const embed = createBaseEmbed(`${NF.gitTag} Release Notes: ${repo} (${notes.version})`).setColor( |
453 | - BrandColors.primary, | |
473 | + Macchiato.yellow, | |
454 | 474 | ); |
455 | 475 | |
456 | 476 | if (notes.breaking && notes.breaking.length > 0) { |
| @@ -516,7 +536,7 @@ export function createHomeDashboardEmbed( | ||
516 | 536 | ]); |
517 | 537 | |
518 | 538 | return createBaseEmbed(`${NF.github} GITBOT Developer Center — @${username}`) |
519 | - .setColor(BrandColors.primary) | |
539 | + .setColor(Macchiato.mauve) | |
520 | 540 | .setDescription( |
521 | 541 | `${tui}\n\n**${NF.warning} NEEDS ATTENTION**\n${attentionItems.join("\n") || `${NF.check} No urgent bottlenecks or failing builds!`}\n\n**${NF.gitTag} RELEASES**\n${latestReleases.join("\n") || "No new releases in followed repositories."}\n\n**${NF.flame} TRENDING ON GITHUB**\n${trendingRepos.join("\n") || "Check /trending for top rising projects."}`, |
522 | 542 | ); |
| @@ -548,7 +568,7 @@ export function createConnectStatusEmbed( | ||
548 | 568 | ]); |
549 | 569 | |
550 | 570 | return createBaseEmbed(`${NF.github} Authentication Center`) |
551 | - .setColor(isConn ? BrandColors.success : BrandColors.secondary) | |
571 | + .setColor(isConn ? Macchiato.green : Macchiato.surface1) | |
552 | 572 | .setDescription(`${tui}\n\n*Credentials are encrypted at rest with AES-256-GCM.*`); |
553 | 573 | } |
554 | 574 | |
| @@ -591,6 +611,7 @@ export function createRepoCommitsEmbed(repo: GitHubRepo, commits: GitHubCommit[] | ||
591 | 611 | iconURL: repo.owner.avatarUrl, |
592 | 612 | url: repo.htmlUrl, |
593 | 613 | }) |
614 | + .setColor(Macchiato.sapphire) | |
594 | 615 | .setURL(repo.htmlUrl) |
595 | 616 | .setDescription(`${tui}\n\n**📜 Direct Commit Links:**\n${markdownLinks || "None"}`); |
596 | 617 | } |
| @@ -623,6 +644,7 @@ export function createRepoPrsEmbed(repo: GitHubRepo, prs: GitHubPullRequest[]): | ||
623 | 644 | iconURL: repo.owner.avatarUrl, |
624 | 645 | url: repo.htmlUrl, |
625 | 646 | }) |
647 | + .setColor(Macchiato.teal) | |
626 | 648 | .setURL(repo.htmlUrl) |
627 | 649 | .setDescription(`${tui}\n\n**🔀 Direct PR Links:**\n${markdownLinks || "None"}`); |
628 | 650 | } |
| @@ -655,6 +677,7 @@ export function createRepoIssuesEmbed(repo: GitHubRepo, issues: GitHubIssue[]): | ||
655 | 677 | iconURL: repo.owner.avatarUrl, |
656 | 678 | url: repo.htmlUrl, |
657 | 679 | }) |
680 | + .setColor(Macchiato.peach) | |
658 | 681 | .setURL(repo.htmlUrl) |
659 | 682 | .setDescription(`${tui}\n\n**🐞 Direct Issue Links:**\n${markdownLinks || "None"}`); |
660 | 683 | } |
| @@ -688,13 +711,14 @@ export function createRepoReleasesEmbed(repo: GitHubRepo, releases: GitHubReleas | ||
688 | 711 | iconURL: repo.owner.avatarUrl, |
689 | 712 | url: repo.htmlUrl, |
690 | 713 | }) |
714 | + .setColor(Macchiato.yellow) | |
691 | 715 | .setURL(repo.htmlUrl) |
692 | 716 | .setDescription(`${tui}\n\n**🏷️ Direct Release Links:**\n${markdownLinks || "None"}`); |
693 | 717 | } |
694 | 718 | |
695 | 719 | export function createSecurityAlertEmbed(secret: DetectedSecret): EmbedBuilder { |
696 | 720 | return createBaseEmbed(`${NF.shield} Sensitive Credential Detected`) |
697 | - .setColor(BrandColors.danger) | |
721 | + .setColor(Macchiato.red) | |
698 | 722 | .setDescription( |
699 | 723 | `A credential pattern matching **${secret.type}** was detected.\n\n**Fingerprint:** \`${secret.fingerprintHash.slice(0, 16)}...\`\n${NF.warning} **Action Required:** Revoke this credential immediately and delete the message below.`, |
700 | 724 | ); |
701 | 725 | |
▼apps/bot/src/ui/tui.ts
+26-11
| @@ -6,15 +6,30 @@ export const TUI_WIDTH = 43; | ||
6 | 6 | export const ANSI = { |
7 | 7 | reset: "\x1b[0m", |
8 | 8 | bold: "\x1b[1m", |
9 | - dim: "\x1b[2;37m", | |
10 | - red: "\x1b[1;31m", | |
11 | - green: "\x1b[1;32m", | |
12 | - yellow: "\x1b[1;33m", | |
13 | - blue: "\x1b[1;34m", | |
14 | - magenta: "\x1b[1;35m", | |
15 | - cyan: "\x1b[1;36m", | |
16 | - white: "\x1b[1;37m", | |
17 | - gray: "\x1b[0;30m", | |
9 | + dim: "\x1b[2;37m", // Macchiato Subtext0 (#a5adcb) | |
10 | + red: "\x1b[1;31m", // Macchiato Red (#ed8796) | |
11 | + green: "\x1b[1;32m", // Macchiato Green (#a6da95) | |
12 | + yellow: "\x1b[1;33m", // Macchiato Peach/Yellow (#eed49f) | |
13 | + blue: "\x1b[1;34m", // Macchiato Blue (#8aadf4) | |
14 | + magenta: "\x1b[1;35m", // Macchiato Mauve (#c6a0f6) | |
15 | + cyan: "\x1b[1;36m", // Macchiato Teal (#8bd5ca) | |
16 | + white: "\x1b[1;37m", // Macchiato Text (#cad3f5) | |
17 | + gray: "\x1b[0;30m", // Macchiato Surface0 (#363a4f) | |
18 | +} as const; | |
19 | + | |
20 | +export const MacchiatoAnsi = { | |
21 | + mauve: ANSI.magenta, | |
22 | + teal: ANSI.cyan, | |
23 | + green: ANSI.green, | |
24 | + peach: ANSI.yellow, | |
25 | + yellow: ANSI.yellow, | |
26 | + red: ANSI.red, | |
27 | + blue: ANSI.blue, | |
28 | + text: ANSI.white, | |
29 | + subtext: ANSI.dim, | |
30 | + surface: ANSI.gray, | |
31 | + reset: ANSI.reset, | |
32 | + bold: ANSI.bold, | |
18 | 33 | } as const; |
19 | 34 | |
20 | 35 | const ANSI_REGEX = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g"); |
| @@ -44,7 +59,7 @@ export function clipAnsi(str: string, maxLen: number): string { | ||
44 | 59 | } |
45 | 60 | |
46 | 61 | export function tuiTopBar(title: string, width = TUI_WIDTH): string { |
47 | - const innerTitle = `${ANSI.cyan}┌─ [ ${ANSI.green}${title}${ANSI.cyan} ] ${ANSI.reset}`; | |
62 | + const innerTitle = `${ANSI.cyan}┌─ [ ${ANSI.magenta}${title}${ANSI.cyan} ] ${ANSI.reset}`; | |
48 | 63 | const rem = width - visibleLength(innerTitle) - 1; |
49 | 64 | return `${innerTitle}${ANSI.cyan}${"─".repeat(Math.max(0, rem))}┐${ANSI.reset}`; |
50 | 65 | } |
| @@ -69,7 +84,7 @@ export function tuiLine(text = "", width = TUI_WIDTH): string { | ||
69 | 84 | } |
70 | 85 | |
71 | 86 | export function tuiPrompt(cmd: string, width = TUI_WIDTH): string { |
72 | - return tuiLine(`${ANSI.white}$ ${cmd}${ANSI.reset}`, width); | |
87 | + return tuiLine(`${ANSI.cyan}$${ANSI.reset} ${ANSI.white}${cmd}${ANSI.reset}`, width); | |
73 | 88 | } |
74 | 89 | |
75 | 90 | export function tuiRow2( |
76 | 91 | |