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
▼apps/bot/src/commands/activity.ts
+44-11
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
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
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
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
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
+// 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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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