Skip to content

Commit 5be9873

Browse files
committed
feat: show ASCII logo + wordmark banner on bare CLI and daemon start
Render the Bitsocial sphere-with-rings logo (braille art) alongside a "Bitsocial" figlet wordmark on bare `bitsocial`, `bitsocial --help`, and `bitsocial daemon` startup. Palette matches bitsocial-web brand tokens (blue-core #1a4fd0, silver-bright #e5e7eb). Respects NO_COLOR/FORCE_COLOR. The banner uses a two-grid paint-map design (SHAPE + COLORS) so each glyph's color can be retouched by flipping a single character. Refs #19
1 parent 4a16ba8 commit 5be9873

File tree

4 files changed

+117
-1
lines changed

4 files changed

+117
-1
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@
6262
},
6363
"hooks": {
6464
"init": [
65-
"./dist/cli/hooks/init/version-hook"
65+
"./dist/cli/hooks/init/version-hook",
66+
"./dist/cli/hooks/init/banner-hook"
6667
],
6768
"prerun": [
6869
"./dist/cli/hooks/prerun/parse-dynamic-flags-hook"

src/cli/ascii-banner.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// ASCII banner, edited as two parallel grids.
2+
//
3+
// SHAPE holds the raw glyphs (braille art + figlet text). Leave these alone
4+
// unless you want to change the art itself.
5+
//
6+
// COLORS is the paint map. Each character corresponds 1:1 to the character
7+
// at the same position in SHAPE:
8+
// B = blue (#1a4fd0) — the sphere
9+
// S = silver (#e5e7eb) — the rings and the "Bitsocial" text
10+
// . = no color (pass the glyph through as-is; use this for spaces)
11+
//
12+
// To retouch the art, find a glyph in SHAPE, then flip the character at the
13+
// same column in the matching COLORS row. A common case: a ring cell came out
14+
// blue because the sphere mask had more dots there — change its 'B' to 'S'.
15+
//
16+
// Both grids MUST have the same number of rows. Each row in COLORS must be at
17+
// least as wide as the corresponding SHAPE row (extra chars are ignored).
18+
// Palette sourced from bitsocialnet/bitsocial-web/about/tailwind.config.ts.
19+
20+
const SHAPE = [
21+
" ⢀⣴⣿⣿⣦⡀ ",
22+
" ⣾⣿⠁⠈⣿⣷⡀ ",
23+
" ⢸⣿⡇ ⢸⣿⣇ ",
24+
" ⢀⣀⣼⣿⣷⣶⣶⣶⣿⣿⣄⡀ ",
25+
" ⢀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣤⡀ ",
26+
" ⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦ 888888b. d8b 888 d8b 888",
27+
" ⢀⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡀ 888 \"88b Y8P 888 Y8P 888",
28+
" ⣀⣤⣤⣶⣶⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣶⣦⣤⣀ 888 .88P 888 888",
29+
"⣰⣿⡿⠛⠉⠉ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⠉⠉⠛⠟⣿⣦ 8888888K. 888 888888 .d8888b .d88b. .d8888b 888 8888b. 888",
30+
"⠻⣷⣦⣤⣀⣀ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ ⢀⣀⣀⣴⣿⠟ 888 \"Y88b 888 888 88K d88\"\"88b d88P\" 888 \"88b 888",
31+
" ⠉⠛⠻⠿⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠿⠟⠛⠉ 888 888 888 888 \"Y8888b. 888 888 888 888 .d888888 888",
32+
" ⠈⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿ 888 d88P 888 Y88b. X88 Y88..88P Y88b. 888 888 888 888",
33+
" ⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟ 8888888P\" 888 \"Y888 88888P' \"Y88P\" \"Y8888P 888 \"Y888888 888",
34+
" ⠈⠻⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⠁ ",
35+
" ⠉⠛⢿⣿⣿⣿⣿⣿⣿⣿⠛⠉ ",
36+
" ⢸⣿⡆ ⣿⡿ ",
37+
" ⠈⢿⣷⡀⣸⣿⠃ ",
38+
" ⠈⠿⣿⡿⠃ "
39+
];
40+
41+
const COLORS = [
42+
"................SSSSSS.......................................................................................",
43+
"................SSSSSSS......................................................................................",
44+
"...............SSSSSSSS......................................................................................",
45+
".............BBBBBBBBSSBB....................................................................................",
46+
"..........BBBBBBBBBBBSSBBBBB.................................................................................",
47+
".........BBBBBBBBBBBBSSBBBBBB...............SSSSSSSS...SSS.SSS...............................SSS..........SSS",
48+
".......BBBBBBBBBBBBBBSSBBBBBBBB.............SSS..SSSS..SSS.SSS...............................SSS..........SSS",
49+
"..SSSSSBBBBBBBBBBBBBBSSBBBBBBBBSSSSSSS......SSS..SSSS......SSS............................................SSS",
50+
"SSSSSS.BBBBBBBBBBBBBBSSBBBBBBBBSSSSSSS......SSSSSSSSS..SSS.SSSSSS.SSSSSSS...SSSSSS...SSSSSSS.SSS..SSSSSS..SSS",
51+
"SSSSSS.BBBBBBBBBBBBBBSSBBBBBBBBSSSSSSS......SSS..SSSSS.SSS.SSS....SSS......SSSSSSSS.SSSSS....SSS.....SSSS.SSS",
52+
"..SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS......SSS....SSS.SSS.SSS....SSSSSSSS.SSS..SSS.SSS......SSS.SSSSSSSS.SSS",
53+
".......BBBBBBBBBBBBBBSSBBBBBBBBBBBBBBB......SSS...SSSS.SSS.SSSSS.......SSS.SSSSSSSS.SSSSS....SSS.SSS..SSS.SSS",
54+
".........BBBBBBBBBBBBSSBBBBBBBBB............SSSSSSSSS..SSS..SSSSS..SSSSSSS..SSSSSS...SSSSSSS.SSS.SSSSSSSS.SSS",
55+
"..........BBBBBBBBBBBSSBBBBBBB...............................................................................",
56+
".............BBBBBBBBSSBBBB..................................................................................",
57+
"...............SSSSSSSSS.....................................................................................",
58+
"...............SSSSSSSS......................................................................................",
59+
"................SSSSSS......................................................................................."
60+
];
61+
62+
const BLUE = "\x1b[38;2;26;79;208m";
63+
const SILVER = "\x1b[38;2;229;231;235m";
64+
const RESET = "\x1b[0m";
65+
66+
function paint(shape: string, colors: string): string {
67+
let out = "";
68+
let current = ".";
69+
for (let i = 0; i < shape.length; i++) {
70+
const glyph = shape[i]!;
71+
const want = colors[i] ?? ".";
72+
if (want !== current) {
73+
if (current !== ".") out += RESET;
74+
if (want === "B") out += BLUE;
75+
else if (want === "S") out += SILVER;
76+
current = want;
77+
}
78+
out += glyph;
79+
}
80+
if (current !== ".") out += RESET;
81+
return out;
82+
}
83+
84+
function supportsColor(): boolean {
85+
if (process.env["NO_COLOR"]) return false;
86+
if (process.env["FORCE_COLOR"]) return true;
87+
return Boolean(process.stdout.isTTY);
88+
}
89+
90+
export function printBanner(): void {
91+
const useColor = supportsColor();
92+
const lines = SHAPE.map((row, i) => (useColor ? paint(row, COLORS[i] ?? "") : row));
93+
process.stdout.write(lines.join("\n") + "\n\n");
94+
}

src/cli/commands/daemon.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
} from "../../util.js";
1616
import type { PKCLoggerType } from "../../util.js";
1717
import { startDaemonServer } from "../../webui/daemon-server.js";
18+
import { printBanner } from "../ascii-banner.js";
1819
import { loadChallengesIntoPKC } from "../../challenge-packages/challenge-utils.js";
1920
import { migrateDataDirectory } from "../../common-utils/data-migration.js";
2021
import { createBsoResolvers } from "../../common-utils/resolvers.js";
@@ -186,6 +187,7 @@ export default class Daemon extends Command {
186187
}
187188

188189
async run() {
190+
printBanner();
189191
// Non-blocking update check — fire-and-forget, won't delay startup
190192
import("../../update/npm-registry.js")
191193
.then(({ fetchLatestVersion }) =>

src/cli/hooks/init/banner-hook.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Hook } from "@oclif/core";
2+
import { printBanner } from "../../ascii-banner.js";
3+
4+
const hook: Hook<"init"> = async function (opts) {
5+
// Skip when --version is being handled by version-hook
6+
if (process.argv.includes("--version")) return;
7+
8+
// Show banner on bare `bitsocial` invocation (no command, no help flag).
9+
// oclif sets opts.id to undefined when no command is matched; argv length > 2
10+
// means the user passed flags/args and expects normal command output.
11+
const bareInvocation = process.argv.length <= 2;
12+
const helpInvocation = !opts.id && (process.argv.includes("--help") || process.argv.includes("-h") || process.argv.includes("help"));
13+
14+
if (bareInvocation || helpInvocation) {
15+
printBanner();
16+
}
17+
};
18+
19+
export default hook;

0 commit comments

Comments
 (0)